<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <title>Shiro笔记 | 净土</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="Shiro; Java安全框架; 认证、授权、加密和会话管理">
<meta property="og:type" content="article">
<meta property="og:title" content="Shiro笔记">
<meta property="og:url" content="http://howiefh.github.io/2015/05/12/shiro-note/index.html">
<meta property="og:site_name" content="净土">
<meta property="og:description" content="Shiro; Java安全框架; 认证、授权、加密和会话管理">
<meta property="og:image" content="http://fh-1.qiniudn.com/shiro/shiro-func.png">
<meta property="og:image" content="http://fh-1.qiniudn.com/shiro/shiro-core.png">
<meta property="og:image" content="http://fh-1.qiniudn.com/shiro/shiro-frame.png">
<meta property="og:image" content="http://fh-1.qiniudn.com/shiro/shiro-authentication.png">
<meta property="og:image" content="http://fh-1.qiniudn.com/shiro/shiro-realm.png">
<meta property="og:image" content="http://fh-1.qiniudn.com/shiro/Shiro-AuthorizationSequence.png">
<meta property="og:updated_time" content="2015-05-21T16:13:33.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Shiro笔记">
<meta name="twitter:description" content="Shiro; Java安全框架; 认证、授权、加密和会话管理">
  
    <link rel="alternative" href="/atom.xml" title="净土" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.ico">
  
  <link href="http://fonts.useso.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  <link rel="stylesheet" href="/css/style.css" type="text/css">
  
<script type="text/javascript">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-40492061-1', 'auto');
ga('send', 'pageview');

</script>


  
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?56d2899c5e919fbf4a7b00de5d1c31dd";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>


</head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">净土</a>
      </h1>
      
        <h2 id="subtitle-wrap">
          <a href="/" id="subtitle">乐不在外而在心，心以为乐，则是境皆乐；心以为苦，则无境不苦。</a>
        </h2>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">Home</a>
        
          <a class="main-nav-link" href="/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-github-link" class="nav-icon" href="https://github.com/howiefh" title="Github" target="_blank"></a>
        
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed" target="_blank"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="Search"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" results="0" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="http://howiefh.github.io"></form>
      </div>
    </div>
  </div>
</header>

      <div class="outer">
        <section id="main"><article id="post-shiro-note" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    
<a href="/2015/05/12/shiro-note/" class="article-date">
  <time datetime="2015-05-12T02:03:26.000Z" itemprop="datePublished">2015-05-12</time>
</a>


    
  <div class="article-category">
    <a class="article-category-link" href="/categories/Java/">Java</a>►<a class="article-category-link" href="/categories/Java/Shiro/">Shiro</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 class="article-title" itemprop="name">
      Shiro笔记
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
		
		<div id="toc" class="toc-article">
			<h2 class="toc-title"><span>Contents</span></h2>
		
			<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#简介"><span class="toc-number">1.</span> <span class="toc-text">简介</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#身份验证"><span class="toc-number">2.</span> <span class="toc-text">身份验证</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Shiro认证过程"><span class="toc-number">2.1.</span> <span class="toc-text">Shiro认证过程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#使用多个Realm的处理机制："><span class="toc-number">2.2.</span> <span class="toc-text">使用多个Realm的处理机制：</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#Authenticator"><span class="toc-number">2.2.1.</span> <span class="toc-text">Authenticator</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AuthenticationStrategy（认证策略）"><span class="toc-number">2.2.2.</span> <span class="toc-text">AuthenticationStrategy（认证策略）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Realm的顺序"><span class="toc-number">2.2.3.</span> <span class="toc-text">Realm的顺序</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#授权"><span class="toc-number">3.</span> <span class="toc-text">授权</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#授权的三要素"><span class="toc-number">3.1.</span> <span class="toc-text">授权的三要素</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#角色"><span class="toc-number">3.2.</span> <span class="toc-text">角色</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#用户"><span class="toc-number">3.3.</span> <span class="toc-text">用户</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#授权实现"><span class="toc-number">3.4.</span> <span class="toc-text">授权实现</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#基于编码的授权实现"><span class="toc-number">3.4.1.</span> <span class="toc-text">基于编码的授权实现</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#基于注解的授权实现"><span class="toc-number">3.4.2.</span> <span class="toc-text">基于注解的授权实现</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#基于JSP_TAG的授权实现"><span class="toc-number">3.4.3.</span> <span class="toc-text">基于JSP TAG的授权实现</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#授权流程"><span class="toc-number">3.5.</span> <span class="toc-text">授权流程</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Realms"><span class="toc-number">4.</span> <span class="toc-text">Realms</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#认证实现"><span class="toc-number">4.1.</span> <span class="toc-text">认证实现</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#授权实现-1"><span class="toc-number">4.2.</span> <span class="toc-text">授权实现</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#会话管理"><span class="toc-number">5.</span> <span class="toc-text">会话管理</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#会话"><span class="toc-number">5.1.</span> <span class="toc-text">　会话</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话管理器"><span class="toc-number">5.2.</span> <span class="toc-text">会话管理器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话监听器"><span class="toc-number">5.3.</span> <span class="toc-text">会话监听器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话存储/持久化"><span class="toc-number">5.4.</span> <span class="toc-text">会话存储/持久化</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话验证"><span class="toc-number">5.5.</span> <span class="toc-text">会话验证</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#编码/加密"><span class="toc-number">6.</span> <span class="toc-text">编码/加密</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#编码/解码"><span class="toc-number">6.1.</span> <span class="toc-text">编码/解码</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#散列算法"><span class="toc-number">6.2.</span> <span class="toc-text">散列算法</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#加密/解密"><span class="toc-number">6.3.</span> <span class="toc-text">加密/解密</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#PasswordService/CredentialsMatcher"><span class="toc-number">6.4.</span> <span class="toc-text">PasswordService/CredentialsMatcher</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#配置"><span class="toc-number">7.</span> <span class="toc-text">配置</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#缓存"><span class="toc-number">8.</span> <span class="toc-text">缓存</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Realm缓存"><span class="toc-number">8.1.</span> <span class="toc-text">Realm缓存</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Session缓存"><span class="toc-number">8.2.</span> <span class="toc-text">Session缓存</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#与Spring的集成"><span class="toc-number">9.</span> <span class="toc-text">与Spring的集成</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#JavaSE"><span class="toc-number">9.1.</span> <span class="toc-text">JavaSE</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Web应用"><span class="toc-number">9.2.</span> <span class="toc-text">Web应用</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Shiro_权限注解"><span class="toc-number">9.3.</span> <span class="toc-text">Shiro 权限注解</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#RememberMe"><span class="toc-number">9.4.</span> <span class="toc-text">RememberMe</span></a></li></ol></li></ol>
		
		</div>
		
        <h2 id="简介">简介</h2><p>Apache Shiro是一个强大易用的Java安全框架，可以帮助我们完成：认证、授权、加密、会话管理、与Web集成、缓存等。</p>
<p><img src="http://fh-1.qiniudn.com/shiro/shiro-func.png" alt="Shiro基本功能"></p>
<ul>
<li>Authentication：身份认证/登录，验证用户是不是拥有相应的身份；</li>
<li>Authorization：授权，即权限验证，验证某个已认证的用户是否拥有某个权限；即判断用户是否能做事情，常见的如：验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限；</li>
<li>Session Manager：会话管理，即用户登录后就是一次会话，在没有退出之前，它的所有信息都在会话中；会话可以是普通JavaSE环境的，也可以是如Web环境的；</li>
<li>Cryptography：加密，保护数据的安全性，如密码加密存储到数据库，而不是明文存储；</li>
<li>Web Support：Web支持，可以非常容易的集成到Web环境；</li>
<li>Caching：缓存，比如用户登录后，其用户信息、拥有的角色/权限不必每次去查，这样可以提高效率；</li>
<li>Concurrency：shiro支持多线程应用的并发验证，即如在一个线程中开启另一个线程，能把权限自动传播过去；</li>
<li>Testing：提供测试支持；</li>
<li>Run As：允许一个用户假装为另一个用户（如果他们允许）的身份进行访问；</li>
<li>Remember Me：记住我，这个是非常常见的功能，即一次登录后，下次再来的话不用登录了。</li>
</ul>
<a id="more"></a>
<p><strong>记住一点，Shiro不会去维护用户、维护权限；这些需要我们自己去设计/提供；然后通过相应的接口注入给Shiro即可。</strong></p>
<p>Shiro的三个核心组件：Subject, SecurityManager 和 Realms. 如下图： </p>
<p><img src="http://fh-1.qiniudn.com/shiro/shiro-core.png" alt="Shiro框架图"></p>
<p>Subject：即“当前操作用户”。但是，在Shiro中，Subject这一概念并不仅仅指人，也可以是第三方进程、后台帐户（Daemon Account）或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途，你可以把它认为是Shiro的“用户”概念。Subject代表了当前用户的安全操作，SecurityManager则管理所有用户的安全操作。与Subject的所有交互都会委托给SecurityManager。 </p>
<p>SecurityManager：它是Shiro框架的核心，典型的Facade模式，Shiro通过SecurityManager来管理内部组件实例，并通过它来提供安全管理的各种服务。可以把它看成 DispatcherServlet 前端控制器 </p>
<p>Realm： Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说，当对用户执行认证（登录）和授权（访问控制）验证时，Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲，Realm实质上是一个安全相关的DAO：它封装了数据源的连接细节，并在需要时将相关数据提供给Shiro。当配置Shiro时，你必须至少指定一个Realm，用于认证和（或）授权。配置多个Realm是可以的，但是至少需要一个。Shiro内置了可以连接大量安全数据源（又名目录）的Realm，如LDAP、关系数据库（JDBC）、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求，你还可以插入代表自定义数据源的自己的Realm实现。 </p>
<p>Shiro完整架构图： </p>
<p><img src="http://fh-1.qiniudn.com/shiro/shiro-frame.png" alt="Shiro框架图"></p>
<ul>
<li><p>Subject (org.apache.shiro.subject.Subject)<br>  正在与软件交互的一个特定的实体“view”（用户、第三方服务、时钟守护任务等）。</p>
</li>
<li><p>SecurityManager (org.apache.shiro.mgt.SecurityManager)<br>  如同上面提到的，SecurityManager 是 Shiro 的核心，它基本上就是一把“保护伞”用来协调它管理的组件使之平稳地一起工作，它也管理着 Shiro 中每一个程序用户的视图，所以它知道每个用户如何执行安全操作。</p>
</li>
<li><p>Authenticator(org.apache.shiro.authc.Authenticator)<br>  Authenticator 是一个组件，负责执行和反馈用户的认证（登录），如果一个用户尝试登录，Authenticator 就开始执行。Authenticator 知道如何协调一个或多个保存有相关用户/帐号信息的 Realm，从这些 Realm中获取这些数据来验证用户的身份以确保用户确实是其表述的那个人。</p>
</li>
<li><p>Authentication Strategy(org.apache.shiro.authc.pam.AuthenticationStrategy)<br>  如果配置了多个 Realm，AuthenticationStrategy 将会协调 Realm 确定在一个身份验证成功或失败的条件（例如，如果在一个方面验证成功了但其他失败了，这次尝试是成功的吗？是不是需要所有方面的验证都成功？还是只需要第一个？）</p>
</li>
<li><p>Authorizer(org.apache.shiro.authz.Authorizer)<br>  Authorizer 是负责程序中用户访问控制的组件，它是最终判断一个用户是否允许做某件事的途径，像 Authenticator 一样，Authorizer 也知道如何通过协调多种后台数据源来访问角色和权限信息，Authorizer 利用这些信息来准确判断一个用户是否可以执行给定的动作。</p>
</li>
<li><p>SessionManager(org.apache.shiro.session.mgt.SessionManager)<br>  SessionManager 知道如何创建并管理用户 Session 生命周期而在所有环境中为用户提供一个强有力的 Session 体验。这在安全框架领域是独一无二—Shiro 具备管理在任何环境下管理用户 Session 的能力，即使没有 Web/Servlet 或者 EJB 容器。默认情况下，Shiro 将使用现有的session（如Servlet Container），但如果环境中没有，比如在一个独立的程序或非 web 环境中，它将使用它自己建立的 session 提供相同的作用，sessionDAO 用来使用任何数据源使 session 持久化。</p>
</li>
<li><p>SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)<br>  SessionDAO 代表 SessionManager 执行 Session 持久（CRUD）动作，它允许任何存储的数据挂接到 session 管理基础上。</p>
</li>
<li><p>CacheManager(org.apache.shiro.cache.CacheManager)<br>  CacheManager 为 Shiro 的其他组件提供创建缓存实例和管理缓存生命周期的功能。因为 Shiro 的认证、授权、会话管理支持多种数据源，所以访问数据源时，使用缓存来提高访问效率是上乘的选择。当下主流开源或企业级缓存框架都可以继承到 Shiro 中，来获取更快更高效的用户体验。</p>
</li>
<li><p>Cryptography (<code>org.apache.shiro.crypto.*</code>)<br>  Cryptography 在安全框架中是一个自然的附加产物，Shiro 的 crypto 包包含了易用且易懂的加密方式，Hashes（即digests）和不同的编码实现。该包里所有的类都易于理解和使用，曾经用过 Java 自身的加密支持的人都知道那是一个具有挑战性的工作，而 Shiro 的加密 API 简化了 java 复杂的工作方式，将加密变得易用。</p>
</li>
<li><p>Realms (org.apache.shiro.realm.Realm)<br>  如同上面提到的，Realm 是 shiro 和你的应用程序安全数据之间的“桥”或“连接”，当实际要与安全相关的数据进行交互如用户执行身份认证（登录）和授权验证（访问控制）时，shiro 从程序配置的一个或多个Realm 中查找这些数据，你需要配置多少个 Realm 便可配置多少个 Realm（通常一个数据源一个），shiro 将会在认证和授权中协调它们。</p>
</li>
</ul>
<h2 id="身份验证">身份验证</h2><p>认证就是验证用户身份的过程。在认证过程中，用户需要提交实体信息(Principals)和凭据信息(Credentials)以检验用户是否合法。最常见的“实体/凭证”组合便是“用户名/密码”组合。 </p>
<h3 id="Shiro认证过程">Shiro认证过程</h3><figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1、获取SecurityManager工厂，此处使用Ini配置文件初始化SecurityManager</span></span><br><span class="line">Factory&lt;SecurityManager&gt; factory = new IniSecurityManagerFactory<span class="params">(<span class="string">"classpath:shiro.ini"</span>)</span>;</span><br><span class="line"><span class="comment">//2、得到SecurityManager实例 并绑定给SecurityUtils</span></span><br><span class="line">SecurityManager securityManager = factory.getInstance<span class="params">()</span>;</span><br><span class="line">SecurityUtils.setSecurityManager<span class="params">(securityManager)</span>;</span><br><span class="line"><span class="comment">//3、获取当前执行用户:</span></span><br><span class="line">Subject currentUser = SecurityUtils.getSubject<span class="params">()</span>;</span><br><span class="line"><span class="comment">//做点跟 Session 相关的事</span></span><br><span class="line">Session session = currentUser.getSession<span class="params">()</span>;</span><br><span class="line">session.setAttribute<span class="params">(<span class="string">"someKey"</span>, <span class="string">"aValue"</span>)</span>;</span><br><span class="line">String value = <span class="params">(String)</span> session.getAttribute<span class="params">(<span class="string">"someKey"</span>)</span>;</span><br><span class="line"><span class="keyword">if</span> <span class="params">(value.equals<span class="params">(<span class="string">"aValue"</span>)</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">log</span>.info<span class="params">(<span class="string">"Retrieved the correct value! ["</span> + value + <span class="string">"]"</span>)</span>;</span><br><span class="line">&#125; </span><br><span class="line"><span class="keyword">if</span> <span class="params">(!currentUser.isAuthenticated<span class="params">()</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//4、创建用户名/密码身份验证Token（即用户身份/凭证）</span></span><br><span class="line">    UsernamePasswordToken token = new UsernamePasswordToken<span class="params">(</span><br><span class="line">            <span class="string">"lonestarr"</span>, <span class="string">"vespa"</span>)</span>;</span><br><span class="line">    token.setRememberMe<span class="params">(<span class="literal">true</span>)</span>;</span><br><span class="line">    try &#123;</span><br><span class="line">        <span class="comment">//5、登录、即身份验证</span></span><br><span class="line">        currentUser.login<span class="params">(token)</span>;</span><br><span class="line">    &#125; catch <span class="params">(UnknownAccountException uae)</span> &#123;</span><br><span class="line">        <span class="built_in">log</span>.info<span class="params">(<span class="string">"There is no user with username of "</span> + token.getPrincipal<span class="params">()</span>)</span>;</span><br><span class="line">    &#125; catch <span class="params">(IncorrectCredentialsException ice)</span> &#123;</span><br><span class="line">        <span class="built_in">log</span>.info<span class="params">(<span class="string">"Password for account "</span> + token.getPrincipal<span class="params">()</span> + <span class="string">" was incorrect!"</span>)</span>;</span><br><span class="line">    &#125; catch <span class="params">(LockedAccountException lae)</span> &#123;</span><br><span class="line">        <span class="built_in">log</span>.info<span class="params">(<span class="string">"The account for username "</span> + token.getPrincipal<span class="params">()</span> + <span class="string">" is locked. "</span> + <span class="string">"Please contact your administrator to unlock it."</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ... 捕获更多异常</span></span><br><span class="line">    catch <span class="params">(AuthenticationException ae)</span> &#123;</span><br><span class="line">        <span class="comment">// 无定义?错误?</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125; </span><br><span class="line"><span class="comment">// 打印主要识别信息 (本例是 username):</span></span><br><span class="line"><span class="built_in">log</span>.info<span class="params">(<span class="string">"User ["</span> + currentUser.getPrincipal<span class="params">()</span> + <span class="string">"] logged in successfully."</span>)</span>;</span><br><span class="line"><span class="comment">// 测试角色:</span></span><br><span class="line"><span class="keyword">if</span> <span class="params">(currentUser.hasRole<span class="params">(<span class="string">"schwartz"</span>)</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">log</span>.info<span class="params">(<span class="string">"May the Schwartz be with you!"</span>)</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">log</span>.info<span class="params">(<span class="string">"Hello, mere mortal."</span>)</span>;</span><br><span class="line">&#125; </span><br><span class="line"><span class="comment">// 测试一个权限 (非（ instance-level） 实例级别)</span></span><br><span class="line"><span class="keyword">if</span> <span class="params">(currentUser.isPermitted<span class="params">(<span class="string">"lightsaber:weild"</span>)</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">log</span>.info<span class="params">(<span class="string">"You may use a lightsaber ring. Use it wisely."</span>)</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">log</span>.info<span class="params">(<span class="string">"Sorry, lightsaber rings are for schwartz masters only."</span>)</span>;</span><br><span class="line">&#125; </span><br><span class="line"><span class="comment">// 一个(非常强大)的实例级别的权限:</span></span><br><span class="line"><span class="keyword">if</span> <span class="params">(currentUser.isPermitted<span class="params">(<span class="string">"winnebago:drive:eagle5"</span>)</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">log</span>.info<span class="params">(<span class="string">"You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. "</span></span><br><span class="line">            + <span class="string">"Here are the keys - have fun!"</span>)</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">log</span>.info<span class="params">(<span class="string">"Sorry, you aren't allowed to drive the 'eagle5' winnebago!"</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//6、完成 - 退出t!</span></span><br><span class="line">currentUser.logout<span class="params">()</span>;</span><br></pre></td></tr></table></figure>
<p>shiro.ini文件：<br><figure class="highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Users and their (optional) assigned roles</span></span><br><span class="line"><span class="comment"># username = password, role1, role2, ..., roleN</span></span><br><span class="line">[users]</span><br><span class="line"><span class="constant">root</span> = secret, admin</span><br><span class="line"><span class="constant">guest</span> = guest, guest</span><br><span class="line"><span class="constant">presidentskroob</span> = 12345, president</span><br><span class="line"><span class="constant">darkhelmet</span> = ludicrousspeed, darklord, schwartz</span><br><span class="line"><span class="constant">lonestarr</span> = vespa, goodguy, schwartz</span><br><span class="line"><span class="comment"># Roles with assigned permissions</span></span><br><span class="line"><span class="comment"># roleName = perm1, perm2, ..., permN</span></span><br><span class="line">[roles]</span><br><span class="line"><span class="constant">admin</span> = *</span><br><span class="line"><span class="constant">schwartz</span> = lightsaber:*</span><br><span class="line"><span class="constant">goodguy</span> = winnebago:drive:eagle5</span><br></pre></td></tr></table></figure></p>
<p>身份验证的主要流程就是：</p>
<ol>
<li>收集用户身份/凭证，即如用户名/密码；</li>
<li>调用 Subject.login 进行登录，如果失败将得到相应的 AuthenticationException 异常，根<br>据异常提示用户错误信息；否则登录成功；</li>
<li>最后调用 Subject.logout 进行退出操作。</li>
</ol>
<p><strong>收集实体/凭据信息</strong> </p>
<p>UsernamePasswordToken支持最常见的用户名/密码的认证机制。同时，由于它实现了RememberMeAuthenticationToken接口，我们可以通过令牌设置“记住我”的功能。但是，“已记住”和“已认证”是有区别的：已记住的用户仅仅是非匿名用户，你可以通过subject.getPrincipals()获取用户信息。但是它并非是完全认证通过的用户，当你访问需要认证用户的功能时，你仍然需要重新提交认证信息。这一区别可以参考亚马逊网站，网站会默认记住登录的用户，再次访问网站时，对于非敏感的页面功能，页面上会显示记住的用户信息，但是当你访问网站账户信息时仍然需要再次进行登录认证。 subject.isAuthenticated()和subject.isRemembered()的值总是相反的。</p>
<p><strong>提交实体/凭据信息</strong> </p>
<p>收集了实体/凭据信息之后，我们可以通过SecurityUtils工具类，获取当前的用户，然后通过调用login方法提交认证。 </p>
<p><strong>认证处理</strong> </p>
<p>如果login方法执行完毕且没有抛出任何异常信息，那么便认为用户认证通过。之后在应用程序任意地方调用SecurityUtils.getSubject() 都可以获取到当前认证通过的用户实例，使用subject.isAuthenticated()判断用户是否已验证都将返回true. 相反，如果login方法执行过程中抛出异常，那么将认为认证失败。Shiro有着丰富的层次鲜明的异常类来描述认证失败的原因，如代码示例。 </p>
<p><strong>登出操作</strong> </p>
<p>登出操作可以通过调用subject.logout()来删除你的登录信息，当执行完登出操作后，Session信息将被清空，subject将被视作为匿名用户。 </p>
<p>以上，是Shiro认证在应用程序中的处理过程，下面将详细解说Shiro认证的内部处理机制。 </p>
<p><img src="http://fh-1.qiniudn.com/shiro/shiro-authentication.png" alt="身份认证流程"></p>
<p>如上图，我们通过Shiro架构图的认证部分，来说明Shiro认证内部的处理顺序： </p>
<ol>
<li>应用程序构建了一个终端用户认证信息的AuthenticationToken 实例后，调用Subject.login方法。 </li>
<li>Subject的实例通常是DelegatingSubject类（或子类）的实例对象，在认证开始时，会委托应用程序设置的securityManager实例调用securityManager.login(token)方法。 </li>
<li>SecurityManager接受到token(令牌)信息后会委托内置的Authenticator的实例（通常都是ModularRealmAuthenticator类的实例）调用authenticator.authenticate(token). ModularRealmAuthenticator在认证过程中会对设置的一个或多个Realm实例进行适配，它实际上为Shiro提供了一个可拔插的认证机制。</li>
<li>如果在应用程序中配置了多个Realm，ModularRealmAuthenticator会根据配置的AuthenticationStrategy(认证策略)来进行多Realm的认证过程。在Realm被调用后，AuthenticationStrategy将对每一个Realm的结果作出响应。注：如果应用程序中仅配置了一个Realm，Realm将被直接调用而无需再配置认证策略。</li>
<li>判断每一个Realm是否支持提交的token，如果支持，Realm将调用getAuthenticationInfo(token); getAuthenticationInfo 方法就是实际认证处理，我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们自定义的认证处理。 </li>
</ol>
<p>Realm接口中需要实现的方法。</p>
<p>String getName(); //返回一个唯一的 Realm 名字<br>boolean supports(AuthenticationToken token); //判断此 Realm 是否支持此 Token<br>AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; //根据 Token 获取认证信息</p>
<p><img src="http://fh-1.qiniudn.com/shiro/shiro-realm.png" alt="Realm"></p>
<p>一般继承AuthorizingRealm即可，需要实现getAuthenticationInfo(AuthenticationToken token)和doGetAuthenticationInfo(PrincipalCollection principals)两个方法</p>
<p>其中主要默认实现如下：<br>org.apache.shiro.realm.text.IniRealm：[users]部分指定用户名/密码及其角色；[roles]部分指定角色即权限信息；<br>org.apache.shiro.realm.text.PropertiesRealm：user.username=password,role1,role2 指定用户名/密码及其角色；role.role1=permission1,permission2 指定角色及权限信息；<br>org.apache.shiro.realm.jdbc.JdbcRealm：<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[main]</span><br><span class="line">jdbcRealm=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.realm</span><span class="class">.jdbc</span><span class="class">.JdbcRealm</span></span><br><span class="line">dataSource=com<span class="class">.alibaba</span><span class="class">.druid</span><span class="class">.pool</span><span class="class">.DruidDataSource</span></span><br><span class="line">dataSource.driverClassName=com<span class="class">.mysql</span><span class="class">.jdbc</span><span class="class">.Driver</span></span><br><span class="line">dataSource.url=jdbc:mysql:<span class="comment">//localhost:3306/shiro</span></span><br><span class="line">dataSource.username=root</span><br><span class="line"><span class="id">#dataSource</span>.password=</span><br><span class="line">jdbcRealm.dataSource=<span class="variable">$dataSource</span></span><br><span class="line">securityManager.realms=<span class="variable">$jdbcRealm</span></span><br></pre></td></tr></table></figure></p>
<h3 id="使用多个Realm的处理机制：">使用多个Realm的处理机制：</h3><p>有些网站既可以用用户名也可以用邮箱、手机登陆，通过多个Realm就可以实现。</p>
<h4 id="Authenticator">Authenticator</h4><p>默认实现是ModularRealmAuthenticator,它既支持单一Realm也支持多个Realm。如果仅配置了一个Realm，ModularRealmAuthenticator 会直接调用该Realm处理认证信息，如果配置了多个Realm，它会根据认证策略来适配Realm，找到合适的Realm执行认证信息。 </p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[main]  </span><br><span class="line">#指定 securityManager 的 authenticator 实现</span><br><span class="line">authenticator=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.authc</span><span class="class">.pam</span><span class="class">.ModularRealmAuthenticator</span></span><br><span class="line">securityManager.authenticator=<span class="variable">$authenticator</span></span><br></pre></td></tr></table></figure>
<h4 id="AuthenticationStrategy（认证策略）">AuthenticationStrategy（认证策略）</h4><p>当应用程序配置了多个Realm时，ModularRealmAuthenticator将根据认证策略来判断认证成功或是失败。<br>例如，如果只有一个Realm验证成功，而其他Realm验证失败，那么这次认证是否成功呢？如果大多数的Realm验证成功了，认证是否就认为成功呢？或者，一个Realm验证成功后，是否还需要判断其他Realm的结果？认证策略就是根据应用程序的需要对这些问题作出决断。 </p>
<p>认证策略是一个无状态的组件，在认证过程中会经过4次的调用： </p>
<ul>
<li>在所有Realm被调用之前</li>
<li>在调用Realm的getAuthenticationInfo 方法之前</li>
<li>在调用Realm的getAuthenticationInfo 方法之后</li>
<li>在所有Realm被调用之后</li>
</ul>
<p>认证策略的另外一项工作就是聚合所有Realm的结果信息封装至一个AuthenticationInfo实例中，并将此信息返回，以此作为Subject的身份信息。 </p>
<p>Shiro有3中认证策略的具体实现： </p>
<ul>
<li>AtLeastOneSuccessfulStrategy    只要有一个（或更多）的Realm验证成功，那么认证将被视为成功</li>
<li>FirstSuccessfulStrategy    第一个Realm验证成功，整体认证将被视为成功，且后续Realm将被忽略</li>
<li>AllSuccessfulStrategy    所有Realm成功，认证才视为成功</li>
</ul>
<p>ModularRealmAuthenticator 内置的认证策略默认实现是AtLeastOneSuccessfulStrategy 方式，因为这种方式也是被广泛使用的一种认证策略。当然，你也可以通过配置文件定义你需要的策略，如：<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[main]  </span><br><span class="line">authcStrategy = org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.authc</span><span class="class">.pam</span><span class="class">.FirstSuccessfulStrategy</span>  </span><br><span class="line">securityManager<span class="class">.authenticator</span><span class="class">.authenticationStrategy</span> = <span class="variable">$authcStrategy</span></span><br></pre></td></tr></table></figure></p>
<h4 id="Realm的顺序">Realm的顺序</h4><p>由刚才提到的认证策略，可以看到Realm在ModularRealmAuthenticator 里面的顺序对认证是有影响的。 </p>
<p>ModularRealmAuthenticator 会读取配置在SecurityManager里的Realm。当执行认证是，它会遍历Realm集合，对所有支持提交的token的Realm调用getAuthenticationInfo 。 </p>
<p>因此，如果Realm的顺序对你使用的认证策略结果有影响，那么你应该在配置文件中明确定义Realm的顺序</p>
<h2 id="授权">授权</h2><p>授权即访问控制，它将判断用户在应用程序中对资源是否拥有相应的访问权限。<br>如，判断一个用户有查看页面的权限，编辑数据的权限，拥有某一按钮的权限，以及是否拥有打印的权限等等。 </p>
<h3 id="授权的三要素">授权的三要素</h3><p>授权有着三个核心元素：权限(permissions)、角色(roles)和用户(users)。 </p>
<p>权限 </p>
<p>权限是Apache Shiro安全机制最核心的元素。它在应用程序中明确声明了被允许的行为和表现。一个格式良好的权限声明可以清晰表达出用户对该资源拥有的权限。</p>
<p>大多数的资源会支持典型的CRUD操作（create,read,update,delete）,但是任何操作建立在特定的资源上才是有意义的。因此，权限声明的根本思想就是建立在资源以及操作上。 </p>
<p>而我们通过权限声明仅仅能了解这个权限可以在应用程序中做些什么，而不能确定谁拥有此权限。权限只描述行为。</p>
<p>于是，我们就需要在应用程序中对用户和权限建立关联。通常的做法就是将权限分配给某个角色，然后将这个角色关联一个或多个用户。 </p>
<p><strong>权限声明及粒度</strong> </p>
<p>Shiro权限声明通常是使用以冒号分隔的表达式。就像前文所讲，一个权限表达式可以清晰的指定资源类型，允许的操作，可访问的数据。同时，Shiro权限表达式支持简单的通配符，可以更加灵活的进行权限设置。 </p>
<p>字符串通配符权限<br>规则：“资源标识符：操作：对象实例 ID” 即对哪个资源的哪个实例可以进行什么操作。其默认支持通配符权限字符串，<code>:</code>表示资源/操作/实例的分割；<code>,</code>表示操作的分割；<code>*</code>表示任意资源/操作/实例。</p>
<p>下面以实例来说明权限表达式。<br>可查询用户数据 <code>User:view</code><br>可查询或编辑用户数据 <code>User:view,edit</code><br>可对用户数据进行所有操作 <code>User:* 或 user</code><br>可编辑id为123的用户数据 <code>User:edit:123</code></p>
<p>注意：通过“system:user:update,delete”验证“system:user:update, system:user:delete”是没问题的，但是反过来是规则不成立。</p>
<h3 id="角色">角色</h3><p>Shiro支持两种角色模式： </p>
<ol>
<li>传统角色：一个角色代表着一系列的操作，当需要对某一操作进行授权验证时，只需判断是否是该角色即可。这种角色权限相对简单、模糊，不利于扩展。 </li>
<li>权限角色：一个角色拥有一个权限的集合。授权验证时，需要判断当前角色是否拥有该权限。这种角色权限可以对该角色进行详细的权限描述，适合更复杂的权限设计。 </li>
</ol>
<p><a href="www.waylau.com/new-rbac-resource-based-access-control/">新的RBAC：基于资源的权限管理(Resource-Based Access Control)</a></p>
<h3 id="用户">用户</h3><p>一个用户本质上是程序中的“谁”，如同我们前面提到的，Subject 实际上是 shiro 的“用户”。</p>
<p>用户（Subjects）通过与角色或权限关联确定是否被允许执行程序内特定的动作，程序数据模型确切定义了 Subject 是否允许做什么事情。Shiro 依赖一个 Realm 实现将你的数据模型关联转换成 Shiro 可以理解的内容</p>
<h3 id="授权实现">授权实现</h3><p>Shiro支持三种方式实现授权过程： </p>
<ul>
<li>编码实现：if(subject.hasRole(“admin”)){//有权限}</li>
<li>注解实现：@RequiresRoles(“admin”)public void hello() {//有权限}</li>
<li>JSP Taglig实现：<code>&lt;shiro:hasRole name=&quot;admin&quot;&gt;&lt;!— 有权限 —&gt;&lt;/shiro:hasRole&gt;</code></li>
</ul>
<h4 id="基于编码的授权实现">基于编码的授权实现</h4><p><strong>基于传统角色授权实现</strong></p>
<p>当需要验证用户是否拥有某个角色时，可以调用Subject 实例的<code>hasRole*</code>方法验证。 </p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Subject currentUser = SecurityUtils.getSubject<span class="params">()</span>;  </span><br><span class="line"><span class="keyword">if</span> <span class="params">(currentUser.hasRole<span class="params">(<span class="string">"administrator"</span>)</span>)</span> &#123;  </span><br><span class="line">    <span class="comment">//显示 admin 按钮</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;  </span><br><span class="line">    <span class="comment">//不显示按钮?  灰色吗？ </span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相关验证方法如下： </p>
<table>
<thead>
<tr>
<th>Subject方法</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>hasRole(String roleName)</code></td>
<td>当用户拥有指定角色时，返回true</td>
</tr>
<tr>
<td><code>hasRoles(List&lt;String&gt; roleNames)</code></td>
<td>按照列表顺序返回相应的一个boolean值数组</td>
</tr>
<tr>
<td><code>hasAllRoles(Collection&lt;String&gt; roleNames)</code></td>
<td>如果用户拥有所有指定角色时，返回true</td>
</tr>
</tbody>
</table>
<p>断言支持 </p>
<p>Shiro还支持以断言的方式进行授权验证。断言成功，不返回任何值，程序继续执行；断言失败时，将抛出异常信息。使用断言，可以使我们的代码更加简洁。 </p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Subject currentUser = SecurityUtils.getSubject<span class="params">()</span>;  </span><br><span class="line"><span class="comment">//保证当前用户是一个银行出纳员</span></span><br><span class="line"><span class="comment">//因此允许开立帐户：</span></span><br><span class="line">currentUser.checkRole<span class="params">(<span class="string">"bankTeller"</span>)</span>;  </span><br><span class="line">openBankAccount<span class="params">()</span>;</span><br></pre></td></tr></table></figure>
<p>断言的相关方法： </p>
<table>
<thead>
<tr>
<th>Subject方法</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>checkRole(String roleName)</code></td>
<td>断言用户是否拥有指定角色</td>
</tr>
<tr>
<td><code>checkRoles(Collection&lt;String&gt; roleNames)</code></td>
<td>断言用户是否拥有所有指定角色</td>
</tr>
<tr>
<td><code>checkRoles(String... roleNames)</code></td>
<td>对上一方法的方法重载</td>
</tr>
</tbody>
</table>
<p><strong>基于权限角色授权实现</strong> </p>
<p>相比传统角色模式，基于权限的角色模式耦合性要更低些，它不会因角色的改变而对源代码进行修改，因此，基于权限的角色模式是更好的访问控制方式。 </p>
<p>它的代码实现有以下几种实现方式： </p>
<p><strong>基于权限对象的实现</strong> </p>
<p>创建org.apache.shiro.authz.Permission的实例，将该实例对象作为参数传递给Subject.isPermitted()进行验证。 </p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Permission printPermission = new PrinterPermission<span class="params">(<span class="string">"laserjet4400n"</span>, <span class="string">"print"</span>)</span>;</span><br><span class="line">Subject currentUser = SecurityUtils.getSubject<span class="params">()</span>;</span><br><span class="line"><span class="keyword">if</span> <span class="params">(currentUser.isPermitted<span class="params">(printPermission)</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//显示 打印 按钮</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">//不显示按钮?  灰色吗？</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相关方法如下： </p>
<table>
<thead>
<tr>
<th>Subject方法</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>isPermitted(Permission p)</code></td>
<td>Subject拥有制定权限时，返回treu</td>
</tr>
<tr>
<td><code>isPermitted(List&lt;Permission&gt; perms)</code></td>
<td>返回对应权限的boolean数组</td>
</tr>
<tr>
<td><code>isPermittedAll(Collection&lt;Permission&gt; perms)</code></td>
<td>Subject拥有所有制定权限时，返回true</td>
</tr>
</tbody>
</table>
<p><strong>基于字符串的实现</strong> </p>
<p>相比笨重的基于对象的实现方式，基于字符串的实现便显得更加简洁。 </p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Subject currentUser = SecurityUtils.getSubject<span class="params">()</span>;</span><br><span class="line"><span class="keyword">if</span> <span class="params">(currentUser.isPermitted<span class="params">(<span class="string">"printer:print:laserjet4400n"</span>)</span>)</span> &#123;</span><br><span class="line">    <span class="comment">//显示 打印 按钮</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">//不显示按钮?  灰色吗？</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>使用冒号分隔的权限表达式是org.apache.shiro.authz.permission.WildcardPermission 默认支持的实现方式。 </p>
<p>这里分别代表了 资源类型:操作:资源ID </p>
<p>类似基于对象的实现相关方法</p>
<table>
<thead>
<tr>
<th>Subject 方法</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>isPermitted(String perm)</td>
<td>如果Subject被允许执行字符串表达的动作或资源访问权限，返回真，否则返回假；</td>
</tr>
<tr>
<td>isPermitted(String… perms)</td>
<td>按照参数顺序返回isPermitted的结果数组，当许多字符串权限需要检查时非常有用（如定制一个复杂的视图时）；</td>
</tr>
<tr>
<td>isPermittedAll(String… perms)</td>
<td>当Subject具备所有字符串定义的权限时返回真，否则返回假。</td>
</tr>
</tbody>
</table>
<p><strong>基于权限对象的断言实现</strong> </p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Subject currentUser = SecurityUtils.getSubject<span class="params">()</span>;</span><br><span class="line"><span class="comment">//担保允许当前用户</span></span><br><span class="line"><span class="comment">//开一个银行帐户：</span></span><br><span class="line">Permission p = new AccountPermission<span class="params">(<span class="string">"open"</span>)</span>;</span><br><span class="line">currentUser.checkPermission<span class="params">(p)</span>;</span><br><span class="line">openBankAccount<span class="params">()</span>;</span><br></pre></td></tr></table></figure>
<p><strong>基于字符串的断言实现</strong> </p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Subject currentUser = SecurityUtils.getSubject<span class="params">()</span>;</span><br><span class="line"><span class="comment">//担保允许当前用户</span></span><br><span class="line"><span class="comment">//开一个银行帐户：</span></span><br><span class="line">currentUser.checkPermission<span class="params">(<span class="string">"account:open"</span>)</span>;</span><br><span class="line">openBankAccount<span class="params">()</span>;</span><br></pre></td></tr></table></figure>
<p><strong>断言实现的相关方法</strong> </p>
<table>
<thead>
<tr>
<th>Subject方法</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>checkPermission(Permission p)</code></td>
<td>断言用户是否拥有制定权限</td>
</tr>
<tr>
<td><code>checkPermission(String perm)</code></td>
<td>断言用户是否拥有制定权限</td>
</tr>
<tr>
<td><code>checkPermissions(Collection&lt;Permission&gt; perms)</code></td>
<td>断言用户是否拥有所有指定权限</td>
</tr>
<tr>
<td><code>checkPermissions(String... perms)</code></td>
<td>断言用户是否拥有所有指定权限</td>
</tr>
</tbody>
</table>
<h4 id="基于注解的授权实现">基于注解的授权实现</h4><p>Shiro注解支持AspectJ、Spring、Google-Guice等，可根据应用进行不同的配置。 </p>
<p>相关的注解：<br>@RequiresAuthentication<br>可以用户类/属性/方法，用于表明当前用户需是经过认证的用户。 </p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="annotation">@RequiresAuthentication</span></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">updateAccount</span><span class="params">(Account userAccount)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这个方法只会被调用在</span></span><br><span class="line">    <span class="comment">//Subject 保证被认证的情况下</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于判断了SecurityUtils.getSubject().isAuthenticated()</p>
<p>@RequiresGuest<br>表明该用户需为”guest”用户 </p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="annotation">@RequiresGuest</span></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">signUp</span><span class="params">(User newUser)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这个方法只会被调用在</span></span><br><span class="line">    <span class="comment">//Subject 未知/匿名的情况下</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于判断了principals == null || principals.isEmpty()</p>
<p>@RequiresPermissions<br>当前用户需拥指定权限 </p>
<figure class="highlight aspectj"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="annotation">@RequiresPermissions</span>(<span class="string">"account:create"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">void</span> <span class="title">createAccount</span><span class="params">(Account account)</span> </span>&#123;</span><br><span class="line">    <span class="comment">//这个方法只会被调用在</span></span><br><span class="line">    <span class="comment">//Subject 允许创建一个 account 的情况下</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相当于判断了subject.isPermitted(“account:create”)</p>
<p>@RequiresRoles<br>当前用户需拥有指定角色<br>相当于判断了subject.hasRole(“administrator”)</p>
<p>@RequiresUser<br>当前用户需为已认证用户或已记住用户 </p>
<h4 id="基于JSP_TAG的授权实现">基于JSP TAG的授权实现</h4><p>Shiro提供了一套JSP标签库来实现页面级的授权控制。标签库描述文件 (TLD)被打包在 META-INF/shiro.tld 文件中的 shiro-web.jar 文件中。 </p>
<p>在使用Shiro标签库前，首先需要在JSP引入shiro标签： </p>
<figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="preprocessor">%</span>@ taglib prefix=<span class="string">"shiro"</span> uri=<span class="string">"http://shiro.apache.org/tags"</span> <span class="preprocessor">%</span>&gt;</span><br></pre></td></tr></table></figure>
<p>下面一一介绍Shiro的标签： </p>
<p>guest标签:验证当前用户是否为“访客”，即未认证（包含未记住）的用户<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">shiro:guest</span>&gt;</span>  </span><br><span class="line">    Hi there!  Please <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"login.jsp"</span>&gt;</span>Login<span class="tag">&lt;/<span class="title">a</span>&gt;</span> or <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"signup.jsp"</span>&gt;</span>Signup<span class="tag">&lt;/<span class="title">a</span>&gt;</span> today!  </span><br><span class="line"><span class="tag">&lt;/<span class="title">shiro:guest</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>user标签:认证通过或已记住的用户<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">shiro:user</span>&gt;</span>  </span><br><span class="line">    Welcome back John!  Not John? Click <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"login.jsp"</span>&gt;</span>here<span class="tag">&lt;<span class="title">a</span>&gt;</span> to login.  </span><br><span class="line"><span class="tag">&lt;/<span class="title">shiro:user</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>authenticated标签:已认证通过的用户。不包含已记住的用户，这是与user标签的区别所在。<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">shiro:authenticated</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"updateAccount.jsp"</span>&gt;</span>Update your contact information<span class="tag">&lt;/<span class="title">a</span>&gt;</span>.  </span><br><span class="line"><span class="tag">&lt;/<span class="title">shiro:authenticated</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>notAuthenticated标签:未认证通过用户，与authenticated标签相对应。与guest标签的区别是，该标签包含已记住用户。<br><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">&lt;shiro:notAuthenticated&gt;</span>  </span><br><span class="line">    Please <span class="variable">&lt;a href="login.jsp"&gt;</span>login<span class="variable">&lt;/a&gt;</span> <span class="keyword">in</span> order <span class="keyword">to</span> update your credit card information.  </span><br><span class="line"><span class="variable">&lt;/shiro:notAuthenticated&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>principal 标签:输出当前用户信息，通常为登录帐号信息<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello, <span class="tag">&lt;<span class="title">shiro:principal</span>/&gt;</span>, how are you today?</span><br></pre></td></tr></table></figure></p>
<p>principal property<br><figure class="highlight fsharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hello, &lt;shiro:principal <span class="class"><span class="keyword">type</span></span>=<span class="string">"com.foo.User"</span> property=<span class="string">"firstName"</span>/&gt;, how are you today?</span><br></pre></td></tr></table></figure></p>
<p>很大程度上等价于<br><figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Hello</span>, &lt;%= <span class="type">SecurityUtils</span>.getSubject<span class="literal">()</span>.getPrincipals<span class="literal">()</span>.oneByType(com.foo.<span class="type">User</span>.<span class="keyword">class</span>).getFirstName<span class="literal">()</span>.toString<span class="literal">()</span> %&gt;, how are you today?</span><br></pre></td></tr></table></figure></p>
<p>hasRole标签:验证当前用户是否属于该角色<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">shiro:hasRole</span> <span class="attribute">name</span>=<span class="value">"administrator"</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"admin.jsp"</span>&gt;</span>Administer the system<span class="tag">&lt;/<span class="title">a</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="title">shiro:hasRole</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>lacksRole标签:与hasRole标签逻辑相反，当用户不属于该角色时验证通过<br><figure class="highlight livecodeserver"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;shiro:lacksRole name=<span class="string">"administrator"</span>&gt;  </span><br><span class="line">    Sorry, you are <span class="operator">not</span> allowed <span class="built_in">to</span> administer <span class="operator">the</span> <span class="keyword">system</span>.  </span><br><span class="line">&lt;/shiro:lacksRole&gt;</span><br></pre></td></tr></table></figure></p>
<p>hasAnyRole标签:验证当前用户是否属于以下任意一个角色。<br><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;shiro:hasAnyRoles <span class="property">name</span>=<span class="string">"developer, project manager, administrator"</span>&gt;  </span><br><span class="line">    You are either a developer, project manager, <span class="keyword">or</span> administrator.  </span><br><span class="line">&lt;/shiro:lacksRole&gt;</span><br></pre></td></tr></table></figure></p>
<p>hasPermission标签:验证当前用户是否拥有制定权限<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">shiro:hasPermission</span> <span class="attribute">name</span>=<span class="value">"user:create"</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"createUser.jsp"</span>&gt;</span>Create a new User<span class="tag">&lt;/<span class="title">a</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="title">shiro:hasPermission</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>lacksPermission标签:与hasPermission标签逻辑相反，当前用户没有制定权限时，验证通过<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">shiro:hasPermission</span> <span class="attribute">name</span>=<span class="value">"user:create"</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"createUser.jsp"</span>&gt;</span>Create a new User<span class="tag">&lt;/<span class="title">a</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="title">shiro:hasPermission</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<h3 id="授权流程">授权流程</h3><p><img src="http://fh-1.qiniudn.com/shiro/Shiro-AuthorizationSequence.png" alt="授权流程"></p>
<ol>
<li>程序或框架代码调用一个 Subject 的<code>hasRole*</code>、<code>checkRole*</code>、<code>isPermitted*</code>或者<code>checkPermission*</code>方法，传递所需的权限或角色。</li>
<li>Subject实例，通常是一个 DelegatingSubject（或子类），通过调用securityManager 与各 <code>hasRole*</code>、<code>checkRole*</code>、<code>isPermitted*</code>或<code>checkPermission*</code> 基本一致的方法将权限或角色传递给程序的 SecurityManager(实现了 org.apache.shiro.authz.Authorizer 接口)。 </li>
<li>接下来SecurityManager会委托内置的Authorizer的实例（默认是ModularRealmAuthorizer 类的实例，类似认证实例，它同样支持一个或多个Realm实例认证）调用相应的授权方法。 </li>
<li>每一个Realm将检查是否实现了相同的 Authorizer 接口。然后，将调用Reaml自己的相应的授权验证方法。 </li>
</ol>
<p>当使用多个Realm时，不同于认证策略处理方式，授权处理过程中： </p>
<ol>
<li>当Realm实现了Authorizer接口<ol>
<li>当调用Realm出现异常时，将立即抛出异常，结束授权验证。 </li>
<li>只要有一个Realm验证成功，那么将认为授权成功，立即返回，结束认证。 </li>
</ol>
</li>
<li>如果 Realm 没有实现 Authorizer 接口，将被忽略。</li>
</ol>
<p><strong>授权顺序</strong></p>
<p>ModularRealmAuthorizer 拥有 SecurityManager 配置的 Realm 实例的入口，当执行一个授权操作时，它将在整个集合中进行迭代（iteration），对于每一个实现 Authorizer 接口的 Realm，调用Realm 各自的 Authorizer 方法（如 hasRole、 checkRole、 isPermitted或 checkPermission）。</p>
<p><strong>配置全局的 PermissionResolver</strong></p>
<p>当执行一个基于字符串的权限检查时，大部分 Shiro 默认的 Realm 将会在执行权限隐含逻辑之前首先把这个字符串转换成一个常用的权限实例。</p>
<p>为了这个转换目的，Shiro 支持 PermissionResolver，大部分 Shiro Realm 使用 PermissionResolver 来支持它们对Authorizer 接口中基于字符串权限方法的实现：当这些方法在Realm上被调用时，将使用PermissionResolver 将字符串转换为权限实例，并执行检查。默认使用内部的 WildcardPermissionResolver</p>
<h2 id="Realms">Realms</h2><p>在认证、授权内部实现机制中都有提到，最终处理都将交给Realm进行处理。因为在Shiro中，最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下，在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说，Realm是专用于安全框架的DAO. </p>
<h3 id="认证实现">认证实现</h3><p>正如前文所提到的，Shiro的认证过程最终会交由Realm执行，这时会调用Realm的getAuthenticationInfo(token)方法。在一个 Realm 执行一个验证尝试之前，它的supports)方法被调用。只有在返回值为 true 的时候它的getAuthenticationInfo(token) 方法才会执行。因此想要禁用认证过程主要supports始终返回false即可。 </p>
<p>该方法主要执行以下操作: </p>
<ol>
<li>检查提交的进行认证的令牌信息 </li>
<li>根据令牌信息从数据源(通常为数据库)中获取用户信息 </li>
<li>确定令牌支持的 credentials (凭证数据)和存储的数据相符。 </li>
<li>验证通过将返回一个封装了用户信息的AuthenticationInfo实例。 </li>
<li>验证失败则抛出AuthenticationException异常信息。 </li>
</ol>
<p>而在我们的应用程序中要做的就是自定义一个Realm类，继承AuthorizingRealm抽象类，重载doGetAuthenticationInfo()，重写获取用户信息的方法。 </p>
<figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException &#123;  </span><br><span class="line">    <span class="built_in">String</span> username <span class="subst">=</span> (<span class="built_in">String</span>)token<span class="built_in">.</span>getPrincipal();</span><br><span class="line">    User user <span class="subst">=</span> userService<span class="built_in">.</span>findByUsername(username);</span><br><span class="line">    <span class="keyword">if</span>(user <span class="subst">==</span> <span class="built_in">null</span>) &#123;</span><br><span class="line">        throw <span class="literal">new</span> UnknownAccountException();<span class="comment">//没找到帐号</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(<span class="built_in">Boolean</span><span class="built_in">.</span><span class="literal">TRUE</span><span class="built_in">.</span><span class="keyword">equals</span>(user<span class="built_in">.</span>getLocked())) &#123;</span><br><span class="line">        throw <span class="literal">new</span> LockedAccountException(); <span class="comment">//帐号锁定</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配，如果觉得人家的不好可以自定义实现</span></span><br><span class="line">    SimpleAuthenticationInfo authenticationInfo <span class="subst">=</span> <span class="literal">new</span> SimpleAuthenticationInfo(</span><br><span class="line">            user<span class="built_in">.</span>getUsername(), <span class="comment">//用户名</span></span><br><span class="line">            user<span class="built_in">.</span>getPassword(), <span class="comment">//密码</span></span><br><span class="line">            ByteSource<span class="built_in">.</span>Util<span class="built_in">.</span><span class="built_in">bytes</span>(user<span class="built_in">.</span>getCredentialsSalt()),<span class="comment">//salt=username+salt</span></span><br><span class="line">            getName()  <span class="comment">//realm name</span></span><br><span class="line">    );</span><br><span class="line">    <span class="keyword">return</span> authenticationInfo;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>凭证匹配</strong></p>
<p>在上述 realm 认证工作流中，一个 Realm 必须较验 Subject 提交的凭证（如密码）是否与存储在数据中的凭证相匹配，如果匹配，验证成功，系统保留已认证的终端用户身份。</p>
<p>AuthenticatingRealm 以及它的子类支持用 CredentialsMatcher 来执行一个凭证对比。</p>
<p>在找到用户数据之后，它和提交的 AuthenticationToken 一起传递给一个 CredentialsMatcher ，后者用来检查提交的数据和存储的数据是否相匹配。Shiro某些 CredentialsMatcher 实现可以使你开箱即用，比如 SimpleCredentialsMatcher(直接比较明文) 和 HashedCredentialsMatcher(可以指定hash策略) 实现</p>
<figure class="highlight ocaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Realm</span> myRealm = <span class="keyword">new</span> com.company.shiro.realm.<span class="type">MyRealm</span><span class="literal">()</span>;</span><br><span class="line"><span class="type">CredentialsMatcher</span> customMatcher = <span class="keyword">new</span> com.company.shiro.realm.<span class="type">CustomCredentialsMatcher</span><span class="literal">()</span>;</span><br><span class="line">myRealm.setCredentialsMatcher(customMatcher);</span><br></pre></td></tr></table></figure>
<h3 id="授权实现-1">授权实现</h3><p>而授权实现则与认证实现非常相似，在我们自定义的Realm中，重载doGetAuthorizationInfo()方法，重写获取用户权限的方法即可。 </p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">protected AuthorizationInfo doGetAuthorizationInfo<span class="params">(PrincipalCollection principals)</span>&#123;  </span><br><span class="line">    String username = <span class="params">(String)</span>principals.getPrimaryPrincipal<span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo<span class="params">()</span>;</span><br><span class="line">    authorizationInfo.setRoles<span class="params">(userService.findRoles<span class="params">(username)</span>)</span>;</span><br><span class="line">    authorizationInfo.setStringPermissions<span class="params">(userService.findPermissions<span class="params">(username)</span>)</span>;</span><br><span class="line">    return authorizationInfo;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="会话管理">会话管理</h2><p>Shiro 提供了完整的企业级会话管理功能，不依赖于底层容器（如 web 容器 tomcat），不管JavaSE 还是 JavaEE 环境都可以使用，提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对 Web 的透明支持、SSO 单点登录的支持等特性。</p>
<h3 id="会话">　会话</h3><p>登录成功后使用 Subject.getSession()即可获取会话；其等价于 Subject.getSession(true)，即如果当前没有创建 Session 对象会创建一个；另外 Subject.getSession(false)，如果当前没有创建 Session 则返回 null</p>
<ul>
<li>session.getId(); //获取会话唯一标识</li>
<li>session.getHost(); //获取 Subject的主机地址,该地址是通过 HostAuthenticationToken.getHost()提供的</li>
<li>session.getTimeout(); //获取过期时间</li>
<li>session.setTimeout(毫秒); //设置会话过期时间</li>
<li>session.getStartTimestamp(); //获取会话启动时间</li>
<li>session.getLastAccessTime(); //获取最后访问时间</li>
<li>session.touch(); //更新最后访问时间</li>
<li>session.stop(); //销毁会话。</li>
<li>session.setAttribute(“key”, “123”); //设置session属性</li>
<li>session.getAttribute(“key”));  //获取session属性</li>
<li>session.removeAttribute(“key”);  //删除会话属性</li>
</ul>
<h3 id="会话管理器">会话管理器</h3><p>SessionManager，名如其意，在应用程序中为所有的 subject 管理Session —— 创建，删除，失效及验证，等等。如同其他在Shiro 中的核心结构组件一样，SessionManager 也是一个由 SecurityManager 维护的顶级组件。</p>
<ul>
<li>Session start(SessionContext context); //启动会话</li>
<li>Session getSession(SessionKey key) throws SessionException; //根据会话 Key 获取会话</li>
</ul>
<p>Shiro 提供了三个默认实现：</p>
<ul>
<li>DefaultSessionManager：DefaultSecurityManager 使用的默认实现，用于 JavaSE 环境；</li>
<li>ServletContainerSessionManager：DefaultWebSecurityManager 使用的默认实现，用于Web环境，其直接使用 Servlet 容器的会话；</li>
<li>DefaultWebSessionManager：用于Web环境的实现，可以替代 ServletContainerSessionManager，自己维护着会话，直接废弃了 Servlet 容器的会话管理。</li>
</ul>
<p>另外可以设置会话的全局过期时间（毫秒为单位），默认 30 分钟：sessionManager. globalSessionTimeout=1800000<br>另外如果使用 ServletContainerSessionManager 进行会话管理，Session 的超时依赖于底层 Servlet 容器的超时时间，可以在 web.xml 中配置其会话的超时时间（分钟为单位）：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">session-config</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">session-timeout</span>&gt;</span>30<span class="tag">&lt;/<span class="title">session-timeout</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">session-config</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>在 Servlet 容器中，默认使用 JSESSIONID Cookie 维护会话，且会话默认是跟容器绑定的；在某些情况下可能需要使用自己的会话机制， 此时我们可以使用 DefaultWebSessionManager来维护会话：</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">sessionIdCookie=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.web</span><span class="class">.servlet</span><span class="class">.SimpleCookie</span></span><br><span class="line">sessionManager=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.web</span><span class="class">.session</span><span class="class">.mgt</span><span class="class">.DefaultWebSessionManager</span></span><br><span class="line">sessionIdCookie.name=sid</span><br><span class="line"><span class="id">#sessionIdCookie</span>.domain=sishuok<span class="class">.com</span></span><br><span class="line"><span class="id">#sessionIdCookie</span>.path=</span><br><span class="line">sessionIdCookie.maxAge=<span class="number">1800</span></span><br><span class="line">sessionIdCookie.httpOnly=true</span><br><span class="line">sessionManager.sessionIdCookie=<span class="variable">$sessionIdCookie</span></span><br><span class="line">sessionManager.sessionIdCookieEnabled=true</span><br><span class="line">securityManager.sessionManager=<span class="variable">$sessionManager</span></span><br></pre></td></tr></table></figure>
<ul>
<li>sessionIdCookie 是 sessionManager 创建会话 Cookie 的模板：</li>
<li>sessionIdCookie.name：设置 Cookie 名字，默认为 JSESSIONID；</li>
<li>sessionIdCookie.domain：设置 Cookie 的域名，默认空，即当前访问的域名；</li>
<li>sessionIdCookie.path：设置 Cookie 的路径，默认空，即存储在域名根下；</li>
<li>sessionIdCookie.maxAge：设置 Cookie 的过期时间，秒为单位，默认-1 表示关闭浏览器时过期 Cookie；</li>
<li>sessionIdCookie.httpOnly：如果设置为 true，则客户端不会暴露给客户端脚本代码，使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击； 此特性需要实现了 Servlet 2.5 MR6及以上版本的规范的 Servlet 容器支持；</li>
<li>sessionManager.sessionIdCookieEnabled：是否启用/禁用 Session Id Cookie，默认是启用的；如果禁用后将不会设置 Session Id Cookie，即默认使用了 Servlet 容器的 JSESSIONID，且通过 URL 重写（URL 中的“;JSESSIONID=id”部分）保存 Session Id。（这里设为false，url并没有重写，需要设置什么吗？)</li>
</ul>
<h3 id="会话监听器">会话监听器</h3><p>会话监听器用于监听会话创建、过期及停止事件。可以实现SessionListener中的onStart、onExpiration、onStop方法</p>
<h3 id="会话存储/持久化">会话存储/持久化</h3><p>Shiro 提供 SessionDAO 用于会话的 CRUD，即 DAO（Data Access Object）模式实现</p>
<figure class="highlight haml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">-<span class="ruby">- <span class="constant">SessionDAO</span></span><br><span class="line"></span>    -<span class="ruby">- <span class="constant">AbstractSessionDAO</span> </span><br><span class="line"></span>        -<span class="ruby">- <span class="constant">CachingSessionDAO</span></span><br><span class="line"></span>            -<span class="ruby">- <span class="constant">EnterpriseCacheSessionDAO</span></span><br><span class="line"></span>        -<span class="ruby">- <span class="constant">MemorySessionDAO</span></span></span><br></pre></td></tr></table></figure>
<p>AbstractSessionDAO提供了SessionDAO的基础实现，如生成会话 ID等；CachingSessionDAO 提供了对开发者透明的会话缓存的功能，只需要设置相应的 CacheManager 即可；MemorySessionDAO 直接在内存中进行会话维护；而 EnterpriseCacheSessionDAO 提供了缓存功能的会话维护，默认情况下使用 MapCache 实现，内部使用 ConcurrentHashMap 保存缓存的会话。</p>
<p>Shiro 提供了使用 Ehcache 进行会话存储，Ehcache 可以配合 TerraCotta 实现容器无关的分布式集群。</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sessionDAO=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.session</span><span class="class">.mgt</span><span class="class">.eis</span><span class="class">.EnterpriseCacheSessionDAO</span></span><br><span class="line">sessionDAO. activeSessionsCacheName=shiro-activeSessionCache</span><br><span class="line">sessionManager.sessionDAO=<span class="variable">$sessionDAO</span></span><br><span class="line">cacheManager = org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.cache</span><span class="class">.ehcache</span><span class="class">.EhCacheManager</span></span><br><span class="line">cacheManager.cacheManagerConfigFile=classpath:ehcache<span class="class">.xml</span></span><br><span class="line">securityManager<span class="class">.cacheManager</span> = <span class="variable">$cacheManager</span></span><br></pre></td></tr></table></figure>
<ul>
<li>sessionDAO. activeSessionsCacheName：设置Session 缓存名字，默认就是shiro-activeSessionCache；</li>
<li>cacheManager：缓存管理器，用于管理缓存的，此处使用 Ehcache 实现；</li>
<li>cacheManager.cacheManagerConfigFile：设置 ehcache 缓存的配置文件；</li>
<li>securityManager.cacheManager：设置 SecurityManager 的 cacheManager，会自动设置实现了CacheManagerAware 接口的相应对象，如 SessionDAO 的 cacheManager；</li>
</ul>
<p>ehcache.xml：<br><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;cache <span class="variable">name=</span><span class="string">"shiro-activeSessionCache"</span></span><br><span class="line">    <span class="variable">maxEntriesLocalHeap=</span><span class="string">"10000"</span></span><br><span class="line">    <span class="variable">overflowToDisk=</span><span class="string">"false"</span></span><br><span class="line">    <span class="variable">eternal=</span><span class="string">"false"</span></span><br><span class="line">    <span class="variable">diskPersistent=</span><span class="string">"false"</span></span><br><span class="line">    <span class="variable">timeToLiveSeconds=</span><span class="string">"0"</span></span><br><span class="line">    <span class="variable">timeToIdleSeconds=</span><span class="string">"0"</span></span><br><span class="line">    <span class="variable">statistics=</span><span class="string">"true"</span>/&gt;</span><br></pre></td></tr></table></figure></p>
<p>Cache 的名字为 shiro-activeSessionCache，即设置的 sessionDAO 的 activeSessionsCacheName 属性值。</p>
<p>用于生成会话 ID，默认就是 JavaUuidSessionIdGenerator，使用 java.util.UUID 生成。<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sessionIdGenerator=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.session</span><span class="class">.mgt</span><span class="class">.eis</span><span class="class">.JavaUuidSessionIdGenerator</span></span><br><span class="line">sessionDAO.sessionIdGenerator=<span class="variable">$sessionIdGenerator</span></span><br></pre></td></tr></table></figure></p>
<h3 id="会话验证">会话验证</h3><p>Shiro 提供了会话验证调度器，用于定期的验证会话是否已过期，如果过期将停止会话；出于性能考虑，一般情况下都是获取会话时来验证会话是否过期并停止会话的；但是如在 web环境中，如果用户不主动退出是不知道会话是否过期的，因此需要定期的检测会话是否过期，Shiro 提供了会话验证调度器 SessionValidationScheduler 来做这件事情。</p>
<p>可以通过如下 ini 配置开启会话验证：<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sessionValidationScheduler=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.session</span><span class="class">.mgt</span><span class="class">.ExecutorServiceSessionValidationScheduler</span></span><br><span class="line">sessionValidationScheduler<span class="class">.interval</span> = <span class="number">3600000</span></span><br><span class="line">sessionValidationScheduler.sessionManager=<span class="variable">$sessionManager</span></span><br><span class="line">sessionManager.globalSessionTimeout=<span class="number">1800000</span></span><br><span class="line">sessionManager.sessionValidationSchedulerEnabled=true</span><br><span class="line">sessionManager.sessionValidationScheduler=<span class="variable">$sessionValidationScheduler</span></span><br></pre></td></tr></table></figure></p>
<ul>
<li>sessionValidationScheduler：会话验证调度器，sessionManager 默认就是使用 ExecutorServiceSessionValidationScheduler， 其使用 JDK 的 ScheduledExecutorService 进行定期调度并验证会话是否过期；</li>
<li>sessionValidationScheduler.interval：设置调度时间间隔，单位毫秒，默认就是 1 小时；</li>
<li>sessionValidationScheduler.sessionManager：设置会话验证调度器进行会话验证时的会话管理器；</li>
<li>sessionManager.globalSessionTimeout：设置全局会话超时时间，默认 30 分钟，即如果 30 分钟内没有访问会话将过期；</li>
<li>sessionManager.sessionValidationSchedulerEnabled：是否开启会话验证器，默认是开启的；</li>
<li>sessionManager.sessionValidationScheduler：设置会话验证调度器，默认就是使用 ExecutorServiceSessionValidationScheduler。</li>
</ul>
<p>Shiro 也提供了使用 Quartz 会话验证调度器,使用时需要导入 shiro-quartz 依赖：</p>
<p>如上会话验证调度器实现都是直接调用 AbstractValidatingSessionManager 的 validateSessions 方法进行验证，其直接调用 SessionDAO 的 getActiveSessions 方法获取所有会话进行验证，如果会话比较多，会影响性能；可以考虑如分页获取会话并进行验证</p>
<p>如果在会话过期时不想删除过期的会话，可以通过如下 ini 配置进行设置：<br><figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sessionManager.<span class="variable">deleteInvalidSessions=</span><span class="constant">false</span></span><br></pre></td></tr></table></figure></p>
<p>会话工厂<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sessionFactory=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.session</span><span class="class">.mgt</span><span class="class">.OnlineSessionFactory</span></span><br><span class="line">sessionManager.sessionFactory=<span class="variable">$sessionFactory</span></span><br></pre></td></tr></table></figure></p>
<h2 id="编码/加密">编码/加密</h2><h3 id="编码/解码">编码/解码</h3><p>Shiro 内部的一些数据的存储/表示都使用了 base64 和 16 进制字符串。</p>
<figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">String </span><span class="keyword">str </span>= <span class="string">"hello"</span><span class="comment">; </span></span><br><span class="line"><span class="keyword">String </span><span class="keyword">base64Encoded </span>= <span class="keyword">Base64.encodeToString(str.getBytes()); </span></span><br><span class="line"><span class="keyword">String </span><span class="keyword">str2 </span>= <span class="keyword">Base64.decodeToString(base64Encoded); </span></span><br><span class="line"><span class="label">Assert.assertEquals</span>(<span class="keyword">str, </span><span class="keyword">str2);</span></span><br></pre></td></tr></table></figure>
<p>还有一个可能经常用到的类 CodecSupport，提供了 toBytes(str,  “utf-8”)  /  toString(bytes, “utf-8”)用于在 byte 数组/String 之间转换。</p>
<h3 id="散列算法">散列算法</h3><p>散列算法一般用于生成数据的摘要信息，是一种不可逆的算法，一般适合存储密码之类的 数据，常见的散列算法如 MD5、SHA 等。一般进行散列时最好提供一个 salt（盐），因为md5解密网站很容易通过散列值得到密码。</p>
<figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">String</span> <span class="built_in">str</span> = <span class="string">"hello"</span>; </span><br><span class="line"><span class="keyword">String</span> salt = <span class="string">"123"</span>; </span><br><span class="line"><span class="keyword">String</span> md5 =<span class="keyword">new</span> Md5Hash(<span class="built_in">str</span>, salt, <span class="number">2</span>).toString();<span class="comment">//还可以转换为  toBase64()/toHex()  做两次hash</span></span><br></pre></td></tr></table></figure>
<p>除了Md5外还有Sha256/Sha1/Sha512</p>
<p>通用的散列支持<br><figure class="highlight processing"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">String</span> <span class="built_in">str</span> = <span class="string">"hello"</span>; </span><br><span class="line"><span class="keyword">String</span> salt = <span class="string">"123"</span>; </span><br><span class="line"><span class="comment">//内部使用Java的 MessageDigest </span></span><br><span class="line"><span class="keyword">String</span> simpleHash =<span class="keyword">new</span> SimpleHash(<span class="string">"SHA-1"</span>, <span class="built_in">str</span>, salt).toString();</span><br></pre></td></tr></table></figure></p>
<p>为了方便使用，Shiro 提供了 HashService，默认提供了 DefaultHashService 实现</p>
<figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">DefaultHashService hashService =new DefaultHashService<span class="params">()</span>; <span class="comment">//默认算法 SHA-512 </span></span><br><span class="line">hashService.setHashAlgorithmName<span class="params">(<span class="string">"SHA-512"</span>)</span>; </span><br><span class="line">hashService.setPrivateSalt<span class="params">(newSimpleByteSource<span class="params">(<span class="string">"123"</span>)</span>)</span>; <span class="comment">//私盐，默认无</span></span><br><span class="line">hashService.setGeneratePublicSalt<span class="params">(<span class="literal">true</span>)</span>;<span class="comment">//是否生成公盐，默认 false </span></span><br><span class="line">hashService.setRandomNumberGenerator<span class="params">(new  SecureRandomNumberGenerator<span class="params">()</span>)</span>;<span class="comment">//用于生成公盐。默认就这个</span></span><br><span class="line">hashService.setHashIterations<span class="params">(<span class="number">1</span>)</span>; <span class="comment">//生成 Hash 值的迭代次数</span></span><br><span class="line">HashRequest request =new HashRequest.Builder<span class="params">()</span> </span><br><span class="line">.setAlgorithmName<span class="params">(<span class="string">"MD5"</span>)</span>.setSource<span class="params">(ByteSource.Util.bytes<span class="params">(<span class="string">"hello"</span>)</span>)</span> </span><br><span class="line">.setSalt<span class="params">(ByteSource.Util.bytes<span class="params">(<span class="string">"123"</span>)</span>)</span>.setIterations<span class="params">(<span class="number">2</span>)</span>.build<span class="params">()</span>; </span><br><span class="line">String hex =hashService.computeHash<span class="params">(request)</span>.toHex<span class="params">()</span>;</span><br></pre></td></tr></table></figure>
<h3 id="加密/解密">加密/解密</h3><p>Shiro 还提供对称式加密/解密算法的支持，如 AES、Blowfish 等；当前还没有提供对非对称加密/解密算法支持，未来版本可能提供。</p>
<p>AES算法<br><figure class="highlight openscad"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">AesCipherService aesCipherService =new AesCipherService<span class="params">()</span>; </span><br><span class="line">aesCipherService.setKeySize<span class="params">(<span class="number">128</span>)</span>; <span class="comment">//设置 key 长度</span></span><br><span class="line"><span class="comment">//生成 key </span></span><br><span class="line">Keykey = aesCipherService.generateNewKey<span class="params">()</span>; </span><br><span class="line">String <span class="built_in">text</span> = <span class="string">"hello"</span>; </span><br><span class="line"><span class="comment">//加密</span></span><br><span class="line">String encrptText = aesCipherService.encrypt<span class="params">(text.getBytes<span class="params">()</span>, key.getEncoded<span class="params">()</span>)</span>.toHex<span class="params">()</span>; </span><br><span class="line"><span class="comment">//解密</span></span><br><span class="line">String text2 = new String<span class="params">(aesCipherService.decrypt<span class="params">(Hex.decode<span class="params">(encrptText)</span>, key.getEncoded<span class="params">()</span>)</span>.getBytes<span class="params">()</span>)</span>; </span><br><span class="line">Assert.assertEquals<span class="params">(text, text2)</span>;</span><br></pre></td></tr></table></figure></p>
<h3 id="PasswordService/CredentialsMatcher">PasswordService/CredentialsMatcher</h3><p>Shiro 提供了 PasswordService 及 CredentialsMatcher 用于提供加密密码及验证密码服务。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">PasswordService</span> </span>&#123; </span><br><span class="line"><span class="comment">//输入明文密码得到密文密码</span></span><br><span class="line"><span class="function">String <span class="title">encryptPassword</span><span class="params">(ObjectplaintextPassword)</span> <span class="keyword">throws</span> IllegalArgumentException</span>; </span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">CredentialsMatcher</span> </span>&#123; </span><br><span class="line"><span class="comment">//匹配用户输入的 token 的凭证（未加密）与系统提供的凭证（已加密）</span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">doCredentialsMatch</span><span class="params">(AuthenticationToken token, AuthenticationInfo info)</span></span>; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Shiro 默认提供了 PasswordService 实现 DefaultPasswordService；CredentialsMatcher 实现PasswordMatcher及HashedCredentialsMatcher（更强大）。</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">[main] </span><br><span class="line">passwordService=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.authc</span><span class="class">.credential</span><span class="class">.DefaultPasswordService</span> </span><br><span class="line">hashService=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.crypto</span><span class="class">.hash</span><span class="class">.DefaultHashService</span> </span><br><span class="line">passwordService.hashService=<span class="variable">$hashService</span> </span><br><span class="line">hashFormat=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.crypto</span><span class="class">.hash</span><span class="class">.format</span><span class="class">.Shiro1CryptFormat</span> </span><br><span class="line">passwordService.hashFormat=<span class="variable">$hashFormat</span> </span><br><span class="line">hashFormatFactory=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.crypto</span><span class="class">.hash</span><span class="class">.format</span><span class="class">.DefaultHashFormatFactory</span> </span><br><span class="line">passwordService.hashFormatFactory=<span class="variable">$hashFormatFactory</span> </span><br><span class="line">passwordMatcher=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.authc</span><span class="class">.credential</span><span class="class">.PasswordMatcher</span> </span><br><span class="line">passwordMatcher.passwordService=<span class="variable">$passwordService</span> </span><br><span class="line">myRealm=com<span class="class">.github</span><span class="class">.zhangkaitao</span><span class="class">.shiro</span><span class="class">.chapter5</span><span class="class">.hash</span><span class="class">.realm</span><span class="class">.MyRealm</span> </span><br><span class="line">myRealm.passwordService=<span class="variable">$passwordService</span> </span><br><span class="line">myRealm.credentialsMatcher=<span class="variable">$passwordMatcher</span> </span><br><span class="line">securityManager.realms=<span class="variable">$myRealm</span></span><br></pre></td></tr></table></figure>
<ol>
<li>passwordService 使用 DefaultPasswordService，如果有必要也可以自定义；</li>
<li>hashService 定义散列密码使用的 HashService，默认使用 DefaultHashService（默认SHA-256 算法）；</li>
<li>hashFormat 用于对散列出的值进行格式化，默认使用 Shiro1CryptFormat，另外提供了Base64Format 和 HexFormat，对于有 salt 的密码请自定义实现 ParsableHashFormat 然后把salt 格式化到散列值中；</li>
<li>hashFormatFactory 用于根据散列值得到散列的密码和 salt； 因为如果使用如 SHA 算法，那么会生成一个 salt，此 salt 需要保存到散列后的值中以便之后与传入的密码比较时使用；默认使用 DefaultHashFormatFactory；</li>
<li>passwordMatcher 使用 PasswordMatcher，其是一个 CredentialsMatcher 实现；</li>
<li>将 credentialsMatcher 赋值给 myRealm， myRealm 间接继承了 AuthenticatingRealm， 其在调用getAuthenticationInfo 方法获取到AuthenticationInfo信息后，会使用 credentialsMatcher 来验证凭据是否匹配，如果不匹配将抛出 IncorrectCredentialsException 异常。</li>
</ol>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">[main] </span><br><span class="line">credentialsMatcher=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.authc</span><span class="class">.credential</span><span class="class">.HashedCredentialsMatcher</span> </span><br><span class="line">credentialsMatcher.hashAlgorithmName=md5 </span><br><span class="line">credentialsMatcher.hashIterations=<span class="number">2</span> </span><br><span class="line">credentialsMatcher.storedCredentialsHexEncoded=true </span><br><span class="line">myRealm=com<span class="class">.github</span><span class="class">.zhangkaitao</span><span class="class">.shiro</span><span class="class">.chapter5</span><span class="class">.hash</span><span class="class">.realm</span><span class="class">.MyRealm2</span> </span><br><span class="line">myRealm.credentialsMatcher=<span class="variable">$credentialsMatcher</span> </span><br><span class="line">securityManager.realms=<span class="variable">$myRealm</span></span><br></pre></td></tr></table></figure>
<ol>
<li>通过 credentialsMatcher.hashAlgorithmName=md5 指定散列算法为 md5，需要和生成密码时的一样；</li>
<li>credentialsMatcher.hashIterations=2，散列迭代次数，需要和生成密码时的意义；</li>
<li>credentialsMatcher.storedCredentialsHexEncoded=true 表示是否存储散列后的密码为 16 进制，需要和生成密码时的一样，默认是 base64；</li>
</ol>
<p>此处最需要注意的就是 HashedCredentialsMatcher 的算法需要和生成密码时的算法一样。 另外 HashedCredentialsMatcher 会自动根据AuthenticationInfo 的类型是否是 SaltedAuthenticationInfo 来获取 credentialsSalt 盐。</p>
<h2 id="配置">配置</h2><p>Apache Shiro的配置主要分为四部分： </p>
<ol>
<li>对象和属性的定义与配置</li>
<li>URL的过滤器配置</li>
<li>静态用户配置</li>
<li>静态角色配置</li>
</ol>
<p>其中，由于用户、角色一般由后台进行操作的动态数据，因此Shiro配置一般仅包含前两项的配置。 </p>
<p>Apache Shiro的大多数组件是基于POJO的，因此我们可以使用POJO兼容的任何配置机制进行配置，例如：Java代码、Sping XML、YAML、JSON、ini文件等等。</p>
<p>Shiro 是从根对象 SecurityManager 进行身份验证和授权的；也就是所有操作都是自它开始的，这个对象是线程安全且整个应用只需要一个即可</p>
<p><strong>INI配置</strong></p>
<ol>
<li>对象名=全限定类名 相当于调用 public 无参构造器创建对象</li>
<li>对象名.属性名=值 相当于调用 setter 方法设置常量值</li>
<li>对象名.属性名=$对象引用 相当于调用 setter 方法设置对象引用</li>
</ol>
<figure class="highlight nix"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">[main]</span><br><span class="line"><span class="comment">#提供了对根对象 securityManager 及其依赖的配置</span></span><br><span class="line"><span class="variable">securityManager=</span>org.apache.shiro.mgt.DefaultSecurityManager</span><br><span class="line">…………</span><br><span class="line">dataSource.<span class="variable">driverClassName=</span>com.mysql.jdbc.Driver</span><br><span class="line"><span class="comment">#常量值注入</span></span><br><span class="line">jdbcRealm.<span class="variable">permissionsLookupEnabled=</span><span class="constant">true</span></span><br><span class="line">…………</span><br><span class="line"><span class="comment">#对象引用值注入</span></span><br><span class="line">securityManager.<span class="variable">realms=</span>$jdbcRealm</span><br><span class="line">…………</span><br><span class="line"><span class="comment">#嵌套属性注入</span></span><br><span class="line">securityManager.authenticator.<span class="variable">authenticationStrategy=</span>$authenticationStrategy</span><br><span class="line"><span class="comment">#byte数组注入</span></span><br><span class="line"><span class="comment">#base64 byte[]</span></span><br><span class="line">authenticator.<span class="variable">bytes=</span><span class="variable">aGVsbG8=</span></span><br><span class="line"><span class="comment">#hex byte[]</span></span><br><span class="line">authenticator.<span class="variable">bytes=</span><span class="number">0</span>x68656c6c6f</span><br><span class="line"><span class="comment">#Array/Set/List注入</span></span><br><span class="line">authenticator.<span class="variable">array=</span><span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span></span><br><span class="line">authenticator.<span class="variable">set=</span>$jdbcRealm,$jdbcRealm</span><br><span class="line"><span class="comment">#Map注入</span></span><br><span class="line">authenticator.<span class="variable">map=</span>$jdbcRealm:$jdbcRealm,<span class="number">1</span>:<span class="number">1</span>,key:abc</span><br><span class="line">[users]</span><br><span class="line"><span class="comment">#提供了对用户/密码及其角色的配置，用户名=密码，角色 1，角色 2</span></span><br><span class="line"><span class="variable">username=</span>password,role1,role2</span><br><span class="line">[roles]</span><br><span class="line"><span class="comment">#提供了角色及权限之间关系的配置，角色=权限 1，权限 2</span></span><br><span class="line"><span class="variable">role1=</span>permission1,permission2</span><br><span class="line">[urls]</span><br><span class="line"><span class="comment">#用于 web，提供了对 web url 拦截相关的配置，url=拦截器[参数]，拦截器</span></span><br><span class="line">/index.<span class="variable">html =</span> anon</span><br><span class="line">/admin<span class="comment">/** = authc, roles[admin], perms["permission1"]</span></span><br></pre></td></tr></table></figure>
<p>XML配置： </p>
<p>主要是对Shiro各个组件的实现进行定义配置，主要组件在前文已做过简单介绍，这里不再一一说明。 </p>
<figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;bean id=<span class="string">"securityManager"</span> class=<span class="string">"org.apache.shiro.mgt.DefaultSecurityManager"</span>&gt;  </span><br><span class="line">        &lt;property name=<span class="string">"cacheManager"</span> <span class="keyword">ref</span>=<span class="string">"cacheManager"</span>/&gt;  </span><br><span class="line">        &lt;property name=<span class="string">"sessionMode"</span> value=<span class="string">"native"</span>/&gt;  </span><br><span class="line">        &lt;!-- <span class="type">Single</span> realm app.  <span class="type">If</span> you have multiple realms, use the 'realms' property instead. --&gt;</span><br><span class="line">        &lt;property name=<span class="string">"realm"</span> <span class="keyword">ref</span>=<span class="string">"myRealm"</span>/&gt;  </span><br><span class="line">        &lt;property name=<span class="string">"sessionManager"</span> <span class="keyword">ref</span>=<span class="string">"sessionManager"</span>/&gt;   </span><br><span class="line">&lt;/bean&gt;</span><br></pre></td></tr></table></figure>
<p>Shiro过滤器的配置 </p>
<p>Shiro主要是通过URL过滤来进行安全管理，这里的配置便是指定具体授权规则定义。 </p>
<figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&lt;bean <span class="property">id</span>=<span class="string">"shiroFilter"</span> <span class="type">class</span>=<span class="string">"org.apache.shiro.spring.web.ShiroFilterFactoryBean"</span>&gt;  </span><br><span class="line">    &lt;<span class="keyword">property</span> <span class="property">name</span>=<span class="string">"securityManager"</span> <span class="keyword">ref</span>=<span class="string">"securityManager"</span>/&gt;  </span><br><span class="line">    &lt;<span class="keyword">property</span> <span class="property">name</span>=<span class="string">"loginUrl"</span> value=<span class="string">"/login.jsp"</span>/&gt;  </span><br><span class="line">    &lt;<span class="keyword">property</span> <span class="property">name</span>=<span class="string">"successUrl"</span> value=<span class="string">"/home.jsp"</span>/&gt;  </span><br><span class="line">    &lt;<span class="keyword">property</span> <span class="property">name</span>=<span class="string">"unauthorizedUrl"</span> value=<span class="string">"/unauthorized.jsp"</span>/&gt; <span class="comment">--&gt;  </span></span><br><span class="line">    &lt;<span class="keyword">property</span> <span class="property">name</span>=<span class="string">"filterChainDefinitions"</span>&gt;  </span><br><span class="line">        &lt;value&gt;  </span><br><span class="line">            <span class="comment"># some example chain definitions:  </span></span><br><span class="line">            /admin/** = authc, roles[admin]  </span><br><span class="line">            /docs/** = authc, perms[document:<span class="command">read</span>]  </span><br><span class="line">            /** = authc  </span><br><span class="line">            <span class="comment"># more URL-to-FilterChain definitions here  </span></span><br><span class="line">        &lt;/value&gt;  </span><br><span class="line">    &lt;/<span class="keyword">property</span>&gt;  </span><br><span class="line">&lt;/bean&gt;</span><br></pre></td></tr></table></figure>
<p>URL过滤器配置说明： </p>
<p>Shiro可以通过配置文件实现基于URL的授权验证。FilterChain定义格式： <code>URL_Ant_Path_Expression = Path_Specific_Filter_Chain</code> </p>
<p>每个URL配置，表示匹配该URL的应用程序请求将由对应的过滤器进行验证。 </p>
<p>例如：<br><figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[urls] </span><br><span class="line">/index.html = anon </span><br><span class="line">/user/create = anon </span><br><span class="line">/user/<span class="keyword">*</span><span class="keyword">*</span> = authc </span><br><span class="line">/admin/<span class="keyword">*</span><span class="keyword">*</span> = authc, roles[administrator] </span><br><span class="line">/rest/<span class="keyword">*</span><span class="keyword">*</span> = authc, rest </span><br><span class="line">/remoting/rpc/<span class="keyword">*</span><span class="keyword">*</span> = authc, perms[<span class="string">"remote:invoke"</span>]</span><br></pre></td></tr></table></figure></p>
<p>URL表达式说明 </p>
<ol>
<li>URL目录是基于HttpServletRequest.getContextPath()此目录设置 </li>
<li>URL可使用通配符，**代表任意子目录 </li>
<li>Shiro验证URL时，URL匹配成功便不再继续匹配查找。所以要注意配置文件中的URL顺序，尤其在使用通配符时。 </li>
</ol>
<p>URL 路径表达式按事先定义好的顺序判断传入的请求，并遵循 FIRST MATCH WINS 这一原则。例如<br><figure class="highlight gherkin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/account/<span class="keyword">*</span><span class="keyword">*</span> = ssl, authc</span><br><span class="line">/account/signup = anon</span><br></pre></td></tr></table></figure></p>
<p>如果传入的请求旨在访问 <code>/account/signup/index.html</code>（所有 ‘anon’ymous 用户都能访问），那么它将永不会被处理！原因是因为<code>/account/*</code>  的模式第一个匹配了传入的请求，“短路”了其余的定义。 </p>
<p>Filter Chain定义说明 </p>
<ol>
<li>一个URL可以配置多个Filter，使用逗号分隔 </li>
<li>当设置多个过滤器时，全部验证通过，才视为通过 </li>
<li>部分过滤器可指定参数，如perms，roles </li>
</ol>
<p>Shiro内置的FilterChain </p>
<table>
<thead>
<tr>
<th>Filter Name</th>
<th>Class</th>
</tr>
</thead>
<tbody>
<tr>
<td>anon</td>
<td>org.apache.shiro.web.filter.authc.AnonymousFilter</td>
</tr>
<tr>
<td>authc</td>
<td>org.apache.shiro.web.filter.authc.FormAuthenticationFilter</td>
</tr>
<tr>
<td>authcBasic</td>
<td>org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter</td>
</tr>
<tr>
<td>logout</td>
<td>org.apache.shiro.web.filter.authc.LogoutFilter</td>
</tr>
<tr>
<td>noSessionCreation</td>
<td>org.apache.shiro.web.filter.session.NoSessionCreationFilter</td>
</tr>
<tr>
<td>perms</td>
<td>org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter</td>
</tr>
<tr>
<td>port</td>
<td>org.apache.shiro.web.filter.authz.PortFilter</td>
</tr>
<tr>
<td>rest</td>
<td>org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter</td>
</tr>
<tr>
<td>roles</td>
<td>org.apache.shiro.web.filter.authz.RolesAuthorizationFilter</td>
</tr>
<tr>
<td>ssl</td>
<td>org.apache.shiro.web.filter.authz.SslFilter</td>
</tr>
<tr>
<td>user</td>
<td>org.apache.shiro.web.filter.authc.UserFilter</td>
</tr>
</tbody>
</table>
<p>OncePerRequestFilter（及其所有子类）支持 Enabling/Disabling 所有请求及 per-request 基础。 一般为所有的请求启用或禁用一个过滤器是通过设置其 enabled 属性为true 或 false。</p>
<figure class="highlight puppet"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[<span class="keyword">main</span>]</span><br><span class="line">ssl.<span class="literal">enabled</span>=<span class="keyword">false</span></span><br></pre></td></tr></table></figure>
<h2 id="缓存">缓存</h2><ul>
<li>CacheManager - 负责所有缓存的主要管理组件，它返回 Cache 实例。</li>
<li>Cache - 维护key/value 对。</li>
<li>CacheManagerAware - 通过想要接收和使用 CacheManager 实例的组件来实现。</li>
</ul>
<p>CacheManager 返回Cache 实例，各种不同的Shiro 组件使用这些Cache 实例来缓存必要的数据。任何实现了 CacheManagerAware 的 Shiro 组件将会自动地接收一个配置好的 CacheManager，该 CacheManager 能够用来获取 Cache 实例。</p>
<h3 id="Realm缓存">Realm缓存</h3><p>Shiro 的 SecurityManager 实现及所有 AuthorizingRealm 实现都实现了 CacheManagerAware 。如果你在 SecurityManager 上设置了 CacheManger，它反过来也会将它设置到实现了CacheManagerAware 的各种不同的 Realm 上（OO delegation）</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">userRealm=com<span class="class">.github</span><span class="class">.zhangkaitao</span><span class="class">.shiro</span><span class="class">.chapter11</span><span class="class">.realm</span><span class="class">.UserRealm</span></span><br><span class="line">userRealm.credentialsMatcher=<span class="variable">$credentialsMatcher</span></span><br><span class="line">userRealm.cachingEnabled=true</span><br><span class="line">userRealm.authenticationCachingEnabled=true</span><br><span class="line">userRealm.authenticationCacheName=authenticationCache</span><br><span class="line">userRealm.authorizationCachingEnabled=true</span><br><span class="line">userRealm.authorizationCacheName=authorizationCache</span><br><span class="line">securityManager.realms=<span class="variable">$userRealm</span></span><br><span class="line">cacheManager=org<span class="class">.apache</span><span class="class">.shiro</span><span class="class">.cache</span><span class="class">.ehcache</span><span class="class">.EhCacheManager</span></span><br><span class="line">cacheManager.cacheManagerConfigFile=classpath:shiro-ehcache<span class="class">.xml</span></span><br><span class="line">securityManager.cacheManager=<span class="variable">$cacheManager</span></span><br></pre></td></tr></table></figure>
<ul>
<li>userRealm.cachingEnabled：启用缓存，默认 false；</li>
<li>userRealm.authenticationCachingEnabled：启用身份验证缓存，即缓存 AuthenticationInfo 信息，默认 false；</li>
<li>userRealm.authenticationCacheName：缓存 AuthenticationInfo 信息的缓存名称；</li>
<li>userRealm. authorizationCachingEnabled：启用授权缓存，即缓存 AuthorizationInfo 信息，默认 false；</li>
<li>userRealm. authorizationCacheName：缓存 AuthorizationInfo 信息的缓存名称；</li>
<li>cacheManager：缓存管理器，此处使用 EhCacheManager，即 Ehcache 实现，需要导入相应的 Ehcache 依赖，请参考 pom.xml；</li>
</ul>
<p>如果凭证数据或授权数据发生改变，需要调用Realm的clearCachedAuthenticationInfo 和 clearCachedAuthorizationInfo方法</p>
<h3 id="Session缓存">Session缓存</h3><p>如 securityManager 实现了 SessionsSecurityManager，其会自动判断 SessionManager 是否实现了 CacheManagerAware 接口，如果实现了会把 CacheManager 设置给它。然后sessionManager 会判断相应的 sessionDAO（如继承自 CachingSessionDAO）是否实现了CacheManagerAware， 如果实现了会把 CacheManager 设置给它</p>
<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sessionDAO=com<span class="class">.github</span><span class="class">.zhangkaitao</span><span class="class">.shiro</span><span class="class">.chapter11</span><span class="class">.session</span><span class="class">.dao</span><span class="class">.MySessionDAO</span></span><br><span class="line">sessionDAO.activeSessionsCacheName=shiro-activeSessionCache</span><br></pre></td></tr></table></figure>
<p>activeSessionsCacheName 默认就是 shiro-activeSessionCache。</p>
<h2 id="与Spring的集成">与Spring的集成</h2><h3 id="JavaSE">JavaSE</h3><p>spring-shiro.xml<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 缓存管理器 使用 Ehcache 实现 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"cacheManager"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.cache.ehcache.EhCacheManager"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"cacheManagerConfigFile"</span> <span class="attribute">value</span>=<span class="value">"classpath:ehcache.xml"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 凭证匹配器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"credentialsMatcher"</span> <span class="attribute">class</span>=<span class="value">"com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">constructor-arg</span> <span class="attribute">ref</span>=<span class="value">"cacheManager"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"hashAlgorithmName"</span> <span class="attribute">value</span>=<span class="value">"md5"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"hashIterations"</span> <span class="attribute">value</span>=<span class="value">"2"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"storedCredentialsHexEncoded"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- Realm 实现 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"userRealm"</span> <span class="attribute">class</span>=<span class="value">"com.github.zhangkaitao.shiro.chapter12.realm.UserRealm"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"userService"</span> <span class="attribute">ref</span>=<span class="value">"userService"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"credentialsMatcher"</span> <span class="attribute">ref</span>=<span class="value">"credentialsMatcher"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"cachingEnabled"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"authenticationCachingEnabled"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"authenticationCacheName"</span> <span class="attribute">value</span>=<span class="value">"authenticationCache"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"authorizationCachingEnabled"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"authorizationCacheName"</span> <span class="attribute">value</span>=<span class="value">"authorizationCache"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 会话 ID 生成器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"sessionIdGenerator"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"</span>/&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 会话 DAO --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"sessionDAO"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"activeSessionsCacheName"</span> <span class="attribute">value</span>=<span class="value">"shiro-activeSessionCache"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionIdGenerator"</span> <span class="attribute">ref</span>=<span class="value">"sessionIdGenerator"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 会话验证调度器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"sessionValidationScheduler"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionValidationInterval"</span> <span class="attribute">value</span>=<span class="value">"1800000"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionManager"</span> <span class="attribute">ref</span>=<span class="value">"sessionManager"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 会话管理器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"sessionManager"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.session.mgt.DefaultSessionManager"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"globalSessionTimeout"</span> <span class="attribute">value</span>=<span class="value">"1800000"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"deleteInvalidSessions"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionValidationSchedulerEnabled"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionValidationScheduler"</span> <span class="attribute">ref</span>=<span class="value">"sessionValidationScheduler"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionDAO"</span> <span class="attribute">ref</span>=<span class="value">"sessionDAO"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 安全管理器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"securityManager"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.mgt.DefaultSecurityManager"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"realms"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">list</span>&gt;</span><span class="tag">&lt;<span class="title">ref</span> <span class="attribute">bean</span>=<span class="value">"userRealm"</span>/&gt;</span><span class="tag">&lt;/<span class="title">list</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="title">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionManager"</span> <span class="attribute">ref</span>=<span class="value">"sessionManager"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"cacheManager"</span> <span class="attribute">ref</span>=<span class="value">"cacheManager"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 相当于调用 SecurityUtils.setSecurityManager(securityManager) --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">class</span>=<span class="value">"org.springframework.beans.factory.config.MethodInvokingFactoryBean"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"staticMethod"</span> <span class="attribute">value</span>=<span class="value">"org.apache.shiro.SecurityUtils.setSecurityManager"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"arguments"</span> <span class="attribute">ref</span>=<span class="value">"securityManager"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- Shiro 生命周期处理器--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"lifecycleBeanPostProcessor"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.spring.LifecycleBeanPostProcessor"</span>/&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>LifecycleBeanPostProcessor 用于在实现了 Initializable 接口的 Shiro bean 初始化时调用 Initializable 接口回调，在实现了 Destroyable 接口的 Shiro bean 销毁时调用 Destroyable 接口回调。 如 UserRealm 就实现了 Initializable， 而 DefaultSecurityManager 实现了 Destroyable。具体可以查看它们的继承关系。 </p>
<h3 id="Web应用">Web应用</h3><p>spring-shiro-web.xml，只列出了和JavaSE不同的项，其中会话管理器和安全管理器和JavaSE稍有不同，其他几个是新加的<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 会话 Cookie 模板 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"sessionIdCookie"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.servlet.SimpleCookie"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">constructor-arg</span> <span class="attribute">value</span>=<span class="value">"sid"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"httpOnly"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"maxAge"</span> <span class="attribute">value</span>=<span class="value">"180000"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 会话管理器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"sessionManager"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.session.mgt.DefaultWebSessionManager"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"globalSessionTimeout"</span> <span class="attribute">value</span>=<span class="value">"1800000"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"deleteInvalidSessions"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionValidationSchedulerEnabled"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionValidationScheduler"</span> <span class="attribute">ref</span>=<span class="value">"sessionValidationScheduler"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionDAO"</span> <span class="attribute">ref</span>=<span class="value">"sessionDAO"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionIdCookieEnabled"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionIdCookie"</span> <span class="attribute">ref</span>=<span class="value">"sessionIdCookie"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 安全管理器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"securityManager"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.mgt.DefaultWebSecurityManager"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"realm"</span> <span class="attribute">ref</span>=<span class="value">"userRealm"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"sessionManager"</span> <span class="attribute">ref</span>=<span class="value">"sessionManager"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"cacheManager"</span> <span class="attribute">ref</span>=<span class="value">"cacheManager"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 基于 Form 表单的身份验证过滤器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"formAuthenticationFilter"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.filter.authc.FormAuthenticationFilter"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"usernameParam"</span> <span class="attribute">value</span>=<span class="value">"username"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"passwordParam"</span> <span class="attribute">value</span>=<span class="value">"password"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"loginUrl"</span> <span class="attribute">value</span>=<span class="value">"/login.jsp"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- Shiro 的 Web 过滤器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"shiroFilter"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.spring.web.ShiroFilterFactoryBean"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"securityManager"</span> <span class="attribute">ref</span>=<span class="value">"securityManager"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"loginUrl"</span> <span class="attribute">value</span>=<span class="value">"/login.jsp"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"unauthorizedUrl"</span> <span class="attribute">value</span>=<span class="value">"/unauthorized.jsp"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"filters"</span>&gt;</span> <span class="comment">&lt;!-- 对应ini文件[filters] --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="title">util:map</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="title">entry</span> <span class="attribute">key</span>=<span class="value">"authc"</span> <span class="attribute">value-ref</span>=<span class="value">"formAuthenticationFilter"</span>/&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="title">util:map</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="title">property</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"filterChainDefinitions"</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="title">value</span>&gt;</span></span><br><span class="line">        /index.jsp = anon</span><br><span class="line">        /unauthorized.jsp = anon</span><br><span class="line">        /login.jsp = authc</span><br><span class="line">        /logout = logout</span><br><span class="line">        /** = user</span><br><span class="line">        <span class="tag">&lt;/<span class="title">value</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="title">property</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>还要在web.xml中添加shiro过滤器</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- Shiro filter--&gt;</span>  </span><br><span class="line"><span class="tag">&lt;<span class="title">filter</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">filter-name</span>&gt;</span>shiroFilter<span class="tag">&lt;/<span class="title">filter-name</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">filter-class</span>&gt;</span>  </span><br><span class="line">        org.springframework.web.filter.DelegatingFilterProxy  </span><br><span class="line">    <span class="tag">&lt;/<span class="title">filter-class</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="title">filter</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;<span class="title">filter-mapping</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">filter-name</span>&gt;</span>shiroFilter<span class="tag">&lt;/<span class="title">filter-name</span>&gt;</span>  </span><br><span class="line">    <span class="tag">&lt;<span class="title">url-pattern</span>&gt;</span>/*<span class="tag">&lt;/<span class="title">url-pattern</span>&gt;</span>  </span><br><span class="line"><span class="tag">&lt;/<span class="title">filter-mapping</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>下面是上述配置所做的事情：</p>
<ol>
<li>EnvironmentLoaderListener 初始化一个Shiro WebEnvironment 实例（其中包含 Shiro 需要的一切操作，包括 SecurityManager ），使得它在 ServletContext 中能够被访问。如果你需要在任何时候获得WebEnvironment 实例，你可以调用WebUtils.getRequiredWebEnvironment（ServletContext）。</li>
<li>ShiroFilter 将使用此 WebEnvironment 对任何过滤的请求执行所有必要的安全操作。</li>
<li>最后，filter-mapping 的定义确保了所有的请求被 ShiroFilter 过滤，建议大多数 Web 应用程序使用以确保任何请求是安全的。</li>
</ol>
<p>通常为了shiro能够很好的工作，这个配置应该在其他过滤器之前</p>
<h3 id="Shiro_权限注解">Shiro 权限注解</h3><p>Shiro 提供了相应的注解用于权限控制，如果使用这些注解就需要使用 AOP 的功能来进行判断，如 Spring AOP；Shiro 提供了 Spring AOP 集成用于权限注解的解析和验证。为了测试，此处使用了 Spring MVC 来测试 Shiro 注解，当然 Shiro 注解不仅仅可以在 web 环境使用，在独立的 JavaSE 中也是可以用的</p>
<p>在spring-mvc.xml中添加权限注解的支持<br><figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;aop:config proxy-target-class=<span class="string">"true"</span>&gt;&lt;/aop:config&gt;</span><br><span class="line">&lt;bean class=<span class="string">"org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"</span>&gt;</span><br><span class="line">    &lt;property name=<span class="string">"securityManager"</span> <span class="keyword">ref</span>=<span class="string">"securityManager"</span>/&gt;</span><br><span class="line">&lt;/bean&gt;</span><br></pre></td></tr></table></figure></p>
<h3 id="RememberMe">RememberMe</h3><p>spring-shiro-web.xml 配置：<br><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"rememberMeCookie"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.servlet.SimpleCookie"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">constructor-arg</span> <span class="attribute">value</span>=<span class="value">"rememberMe"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"httpOnly"</span> <span class="attribute">value</span>=<span class="value">"true"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"maxAge"</span> <span class="attribute">value</span>=<span class="value">"2592000"</span>/&gt;</span><span class="comment">&lt;!-- 30 天 --&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- rememberMe 管理器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"rememberMeManager"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.mgt.CookieRememberMeManager"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"cipherKey"</span> <span class="attribute">value</span>=<span class="value">"#&#123;T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')&#125;"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"cookie"</span> <span class="attribute">ref</span>=<span class="value">"rememberMeCookie"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 安全管理器 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"securityManager"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.mgt.DefaultWebSecurityManager"</span>&gt;</span></span><br><span class="line">    ……</span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"rememberMeManager"</span> <span class="attribute">ref</span>=<span class="value">"rememberMeManager"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">bean</span> <span class="attribute">id</span>=<span class="value">"formAuthenticationFilter"</span> <span class="attribute">class</span>=<span class="value">"org.apache.shiro.web.filter.authc.FormAuthenticationFilter"</span>&gt;</span></span><br><span class="line">    ……</span><br><span class="line">    <span class="tag">&lt;<span class="title">property</span> <span class="attribute">name</span>=<span class="value">"rememberMeParam"</span> <span class="attribute">value</span>=<span class="value">"rememberMe"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">bean</span>&gt;</span></span><br></pre></td></tr></table></figure></p>
<p>rememberMe 管理器，cipherKey 是加密 rememberMe Cookie 的密钥；默认 AES 算法；设置 securityManager 安全管理器的 rememberMeManager；rememberMeParam，即 rememberMe 请求参数名，请求参数是 boolean 类型，true 表示 rememberMe。</p>
<p><code>/authenticated.jsp = authc</code> 表示访问该地址用户必须身份验证通过（ Subject.isAuthenticated()==true）；而<code>/** = user</code>表示访问该地址的用户是身份验证通过或 RememberMe 登录的都可以。</p>

      
    </div>
    <footer class="article-footer">
	  
	  <!-- 百度分享 Start -->
	  <div class="bdsharebuttonbox"><a href="#" class="bds_more" data-cmd="more"></a><a href="#" class="bds_qzone" data-cmd="qzone" title="分享到QQ空间"></a><a href="#" class="bds_tsina" data-cmd="tsina" title="分享到新浪微博"></a><a href="#" class="bds_tqq" data-cmd="tqq" title="分享到腾讯微博"></a><a href="#" class="bds_renren" data-cmd="renren" title="分享到人人网"></a><a href="#" class="bds_weixin" data-cmd="weixin" title="分享到微信"></a></div>
	  <!-- 百度分享 End -->
	  
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Shiro/">Shiro</a></li></ul>

	  
<span>
Updated:<time datetime="2015-05-21T16:13:33.000Z" itemprop="dateModified">2015-05-22</time>
</span>


    </footer>
  </div>
  
    
<nav id="article-nav">
  
    <a href="/2015/05/19/shiro-cas-single-sign-on/" id="article-nav-newer" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Newer</strong>
      <div class="article-nav-title">
        
          Shiro &amp; CAS 实现单点登录
        
      </div>
    </a>
  
  
    <a href="/2015/05/03/freemarker-XML-processing-note/" id="article-nav-older" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Older</strong>
      <div class="article-nav-title">FreeMarker-XML处理笔记</div>
    </a>
  
</nav>

  
</article>



<!-- 多说评论框 start -->

<section id="comments">
  <div class="ds-thread"  data-thread-key="/2015/05/12/shiro-note/" data-title="Shiro笔记" data-url="http://howiefh.github.io/2015/05/12/shiro-note/" id="ds_thread">
    <noscript>Please enable JavaScript to view the <a href="//duoshuo.com/?ref_noscript">comments powered by duoshuo.</a></noscript>
  </div>
</section>

<!-- 多说评论框 end -->
</section>
        
          
  <div id="toc" class="toc-aside">
  <h2 class="toc-title">Contents</h2>
    
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#简介"><span class="toc-number">1.</span> <span class="toc-text">简介</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#身份验证"><span class="toc-number">2.</span> <span class="toc-text">身份验证</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Shiro认证过程"><span class="toc-number">2.1.</span> <span class="toc-text">Shiro认证过程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#使用多个Realm的处理机制："><span class="toc-number">2.2.</span> <span class="toc-text">使用多个Realm的处理机制：</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#Authenticator"><span class="toc-number">2.2.1.</span> <span class="toc-text">Authenticator</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AuthenticationStrategy（认证策略）"><span class="toc-number">2.2.2.</span> <span class="toc-text">AuthenticationStrategy（认证策略）</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#Realm的顺序"><span class="toc-number">2.2.3.</span> <span class="toc-text">Realm的顺序</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#授权"><span class="toc-number">3.</span> <span class="toc-text">授权</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#授权的三要素"><span class="toc-number">3.1.</span> <span class="toc-text">授权的三要素</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#角色"><span class="toc-number">3.2.</span> <span class="toc-text">角色</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#用户"><span class="toc-number">3.3.</span> <span class="toc-text">用户</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#授权实现"><span class="toc-number">3.4.</span> <span class="toc-text">授权实现</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#基于编码的授权实现"><span class="toc-number">3.4.1.</span> <span class="toc-text">基于编码的授权实现</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#基于注解的授权实现"><span class="toc-number">3.4.2.</span> <span class="toc-text">基于注解的授权实现</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#基于JSP_TAG的授权实现"><span class="toc-number">3.4.3.</span> <span class="toc-text">基于JSP TAG的授权实现</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#授权流程"><span class="toc-number">3.5.</span> <span class="toc-text">授权流程</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Realms"><span class="toc-number">4.</span> <span class="toc-text">Realms</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#认证实现"><span class="toc-number">4.1.</span> <span class="toc-text">认证实现</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#授权实现-1"><span class="toc-number">4.2.</span> <span class="toc-text">授权实现</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#会话管理"><span class="toc-number">5.</span> <span class="toc-text">会话管理</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#会话"><span class="toc-number">5.1.</span> <span class="toc-text">　会话</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话管理器"><span class="toc-number">5.2.</span> <span class="toc-text">会话管理器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话监听器"><span class="toc-number">5.3.</span> <span class="toc-text">会话监听器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话存储/持久化"><span class="toc-number">5.4.</span> <span class="toc-text">会话存储/持久化</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#会话验证"><span class="toc-number">5.5.</span> <span class="toc-text">会话验证</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#编码/加密"><span class="toc-number">6.</span> <span class="toc-text">编码/加密</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#编码/解码"><span class="toc-number">6.1.</span> <span class="toc-text">编码/解码</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#散列算法"><span class="toc-number">6.2.</span> <span class="toc-text">散列算法</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#加密/解密"><span class="toc-number">6.3.</span> <span class="toc-text">加密/解密</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#PasswordService/CredentialsMatcher"><span class="toc-number">6.4.</span> <span class="toc-text">PasswordService/CredentialsMatcher</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#配置"><span class="toc-number">7.</span> <span class="toc-text">配置</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#缓存"><span class="toc-number">8.</span> <span class="toc-text">缓存</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Realm缓存"><span class="toc-number">8.1.</span> <span class="toc-text">Realm缓存</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Session缓存"><span class="toc-number">8.2.</span> <span class="toc-text">Session缓存</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#与Spring的集成"><span class="toc-number">9.</span> <span class="toc-text">与Spring的集成</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#JavaSE"><span class="toc-number">9.1.</span> <span class="toc-text">JavaSE</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Web应用"><span class="toc-number">9.2.</span> <span class="toc-text">Web应用</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Shiro_权限注解"><span class="toc-number">9.3.</span> <span class="toc-text">Shiro 权限注解</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#RememberMe"><span class="toc-number">9.4.</span> <span class="toc-text">RememberMe</span></a></li></ol></li></ol>
    
  </div>

<aside id="sidebar">

  
    
<div class="widget-wrap">
  <h3 class="widget-title">ABOUT ME</h3>
  <ul class="widget about-me">
    
    <li><img class="author" title="About me" src="http://fh-1.qiniudn.com/okal-eltocat.jpg" /></li>
    
    
    <li>Hi,I'm FengHao.</li>
    
    <li>I'll share something interesting and my learning experience with you at this blog.</li>
    
    <li>前博客:<a href="http://hi.baidu.com/idea_star" target="_BLANK">百度空间</a></li>
    
  </ul>
</div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Categories</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Android/">Android</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/C/">C</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Database/">Database</a><span class="category-list-count">13</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Database/MongoDB/">MongoDB</a><span class="category-list-count">10</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Database/MySQL/">MySQL</a><span class="category-list-count">2</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/">Eclipse</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/FTP/">FTP</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Git/">Git</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Hexo/">Hexo</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Java/">Java</a><span class="category-list-count">20</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Java/FreeMarker/">FreeMarker</a><span class="category-list-count">3</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Java/Shiro/">Shiro</a><span class="category-list-count">2</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/JavaEE/">JavaEE</a><span class="category-list-count">4</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/JavaEE/Hibernate/">Hibernate</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/JavaEE/JSP/">JSP</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/JavaEE/Spring/">Spring</a><span class="category-list-count">2</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/JavaScript/">JavaScript</a><span class="category-list-count">5</span><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/JavaScript/jQuery/">jQuery</a><span class="category-list-count">1</span></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Linux/">Linux</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/PHP/">PHP</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Suse/">Suse</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Ubuntu/">Ubuntu</a><span class="category-list-count">5</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/Vim/">Vim</a><span class="category-list-count">8</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/编程/">编程</a><span class="category-list-count">2</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/软件/">软件</a><span class="category-list-count">1</span></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Tag Cloud</h3>
    <div class="widget tagcloud">
      <a href="/tags/Android/" style="font-size: 14.75px; color: #65bfa7">Android</a> <a href="/tags/C/" style="font-size: 13.88px; color: #71c1c2">C</a> <a href="/tags/CAS/" style="font-size: 13px; color: #7dc3de">CAS</a> <a href="/tags/Chrome/" style="font-size: 13px; color: #7dc3de">Chrome</a> <a href="/tags/Clean-Code/" style="font-size: 13.88px; color: #71c1c2">Clean Code</a> <a href="/tags/Database/" style="font-size: 13px; color: #7dc3de">Database</a> <a href="/tags/Eclipse/" style="font-size: 13px; color: #7dc3de">Eclipse</a> <a href="/tags/FTP/" style="font-size: 13.88px; color: #71c1c2">FTP</a> <a href="/tags/FreeMarker/" style="font-size: 14.75px; color: #65bfa7">FreeMarker</a> <a href="/tags/Gcc/" style="font-size: 13px; color: #7dc3de">Gcc</a> <a href="/tags/Git/" style="font-size: 14.75px; color: #65bfa7">Git</a> <a href="/tags/Github-Pages/" style="font-size: 13px; color: #7dc3de">Github Pages</a> <a href="/tags/Hexo/" style="font-size: 14.75px; color: #65bfa7">Hexo</a> <a href="/tags/Hibernate/" style="font-size: 13px; color: #7dc3de">Hibernate</a> <a href="/tags/JSP/" style="font-size: 13px; color: #7dc3de">JSP</a> <a href="/tags/JVM/" style="font-size: 14.75px; color: #65bfa7">JVM</a> <a href="/tags/Java/" style="font-size: 14.75px; color: #65bfa7">Java</a> <a href="/tags/JavaMail/" style="font-size: 13px; color: #7dc3de">JavaMail</a> <a href="/tags/JavaScript/" style="font-size: 16.5px; color: #4dbc6f">JavaScript</a> <a href="/tags/Linux/" style="font-size: 13.88px; color: #71c1c2">Linux</a> <a href="/tags/Log/" style="font-size: 13px; color: #7dc3de">Log</a> <a href="/tags/Markdown/" style="font-size: 13.88px; color: #71c1c2">Markdown</a> <a href="/tags/MongoDB/" style="font-size: 20px; color: #1db400">MongoDB</a> <a href="/tags/MySQL/" style="font-size: 13.88px; color: #71c1c2">MySQL</a> <a href="/tags/PHP/" style="font-size: 16.5px; color: #4dbc6f">PHP</a> <a href="/tags/Rhythmbox/" style="font-size: 13px; color: #7dc3de">Rhythmbox</a> <a href="/tags/SSO/" style="font-size: 13px; color: #7dc3de">SSO</a> <a href="/tags/Servlet/" style="font-size: 13px; color: #7dc3de">Servlet</a> <a href="/tags/Shiro/" style="font-size: 13.88px; color: #71c1c2">Shiro</a> <a href="/tags/Spring/" style="font-size: 13.88px; color: #71c1c2">Spring</a> <a href="/tags/Suse/" style="font-size: 13px; color: #7dc3de">Suse</a> <a href="/tags/Thinking-in-Java/" style="font-size: 19.13px; color: #29b61c">Thinking in Java</a> <a href="/tags/Ubuntu/" style="font-size: 17.38px; color: #41ba53">Ubuntu</a> <a href="/tags/Vim/" style="font-size: 18.25px; color: #35b838">Vim</a> <a href="/tags/VirtualBox/" style="font-size: 13px; color: #7dc3de">VirtualBox</a> <a href="/tags/Vsftpd/" style="font-size: 13px; color: #7dc3de">Vsftpd</a> <a href="/tags/jQuery/" style="font-size: 13px; color: #7dc3de">jQuery</a> <a href="/tags/pam-mysql/" style="font-size: 13px; color: #7dc3de">pam_mysql</a> <a href="/tags/小米/" style="font-size: 13px; color: #7dc3de">小米</a> <a href="/tags/软件/" style="font-size: 15.63px; color: #59bd8b">软件</a>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Archives</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/09/">September 2015</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/08/">August 2015</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/07/">July 2015</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/05/">May 2015</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/04/">April 2015</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/03/">March 2015</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/02/">February 2015</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/12/">December 2014</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/11/">November 2014</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/10/">October 2014</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/09/">September 2014</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/08/">August 2014</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/07/">July 2014</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/06/">June 2014</a><span class="archive-list-count">6</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/05/">May 2014</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/04/">April 2014</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/03/">March 2014</a><span class="archive-list-count">3</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/02/">February 2014</a><span class="archive-list-count">11</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/01/">January 2014</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2013/12/">December 2013</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2013/11/">November 2013</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2013/08/">August 2013</a><span class="archive-list-count">3</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2013/07/">July 2013</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2013/06/">June 2013</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2013/05/">May 2013</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2013/04/">April 2013</a><span class="archive-list-count">3</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2012/03/">March 2012</a><span class="archive-list-count">1</span></li></ul>
    </div>
  </div>


  
    <div class="widget-wrap">
  <h3 class="widget-title">Calendar</h3>
  <div class="widget">
    <div id="g-calendar" class="g-calendar">
        <span class="g-calendar-prev"></span>
		 
        <span class="g-calendar-back"></span>
        <span class="g-calendar-now"></span>
		 
        <span class="g-calendar-next"></span>
        <div class="g-calendar-hd"></div>
        <div class="g-calendar-tit"></div>
        <div class="g-calendar-bd"></div>
    </div>
  </div>
</div>
<script type="text/javascript">
function LGY_calendar(wrapId, options){
    this.oWrap = this.getId(wrapId);
    this.oHead = this.getByClassName('g-calendar-hd',this.oWrap)[0];
    this.oBody = this.getByClassName('g-calendar-bd',this.oWrap)[0];
    this.oTit = this.getByClassName('g-calendar-tit',this.oWrap)[0];
    this.oPrev = this.getByClassName('g-calendar-prev',this.oWrap)[0];
    this.oNext = this.getByClassName('g-calendar-next',this.oWrap)[0];
    this.oNow = this.getByClassName('g-calendar-now',this.oWrap)[0];
    this.oBack = this.getByClassName('g-calendar-back',this.oWrap)[0];
    this.init();
}
LGY_calendar.prototype = {
    ///////////获取ID元素
    getId:function(id){
        return document.getElementById(id);
    },
    ////////获取css类名元素
    getByClassName:function(className,parent){
        var elem = [],
            node = parent != undefined&&parent.nodeType==1?parent.getElementsByTagName('*'):document.getElementsByTagName('*'),
            p = new RegExp("(^|\\s)"+className+"(\\s|$)");
        for(var n=0,i=node.length;n<i;n++){
            if(p.test(node[n].className)){
                elem.push(node[n]);
            }
        }
        return elem;
    },
    //填充日历
    fillDate:function(year,month){
        //本月份第一天是星期几-为显示上个月的天数做铺垫
        var first_day = new Date(year,month,1).getDay(),
        //如果刚好是星期天，则空出一行（显示上个月的天数）
            first_day = first_day == 0?first_day=7:first_day;
        //本月份最后一天是几号
        var final_date = new Date(year,month+1,0).getDate(),
        //上个月的最后一天是几号
            last_date = new Date(year,month,0).getDate(),
        //剩余的格子数--即排在末尾的格子数
            surplus = 42 - first_day - final_date;
        //设置年的链接
        var yearHead = "<a href='/" + "archives/" + year + "/'>" + year + " "+ "</a>"; 
        //设置年的链接
        var monthHead = "";
        var month1 = month + 1;
        if (month1 < 10) {
            monthHead = "<a href='/" + "archives/" + year + "/" + "0" + month1 + "/'>" + " " + month1 + " " + "</a>";
        } else {
            monthHead = "<a href='/" + "archives/" + year + "/" + month1 + "/'>" + " " + month1 + " " + "</a>";
        }
        //设置表头的日历
        this.oHead.innerHTML = yearHead+'年'+monthHead+'月';
        //填充日历执行
        var html = '';
        //上个月的显示天数
        for(var i=0;i<first_day;i++){
            html+='<span class="g-calendar-grey">'+(last_date-(first_day-1)+i)+'</span>';
        }
        //本月的显示天数
        var postdate = new Date("Tue May 12 2015 10:03:26 GMT+0800"); 
        if (true && postdate.getFullYear() == year && postdate.getMonth() == month) { 
            html += '<span>1</span><span><a href="/2015/05/02/freemarker-program-development-note/" title="FreeMarker-程序开发指南笔记">2</a></span><span><a href="/2015/05/03/freemarker-XML-processing-note/" title="FreeMarker-XML处理笔记">3</a></span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>10</span><span>11</span><span><a href="/2015/05/12/shiro-note/" title="Shiro笔记">12</a></span><span>13</span><span>14</span><span>15</span><span>16</span><span>17</span><span>18</span><span><a href="/2015/05/19/shiro-cas-single-sign-on/" title="Shiro & CAS 实现单点登录">19</a></span><span>20</span><span>21</span><span><a href="/2015/05/22/vim-install-youcompleteme-plugin/" title="Vim 自动补全插件 YouCompleteMe 安装与配置">22</a></span><span>23</span><span>24</span><span>25</span><span>26</span><span>27</span><span>28</span><span>29</span><span>30</span>';
        } else {
            for(var j=0;j<final_date;j++){        
                html+='<span id="d'+(j+1)+'">'+(j+1)+'</span>';
            }
        }
        //下个月的显示天数
        for(var k=0;k<surplus;k++){
            html+='<span class="g-calendar-grey">'+(k+1)+'</span>';
        }
        //fill
        this.oBody.innerHTML = html;
        // 当前状态
        if(year==this.c_year&&this.c_month==month){
            this.oBody.getElementsByTagName('span')[first_day+this.date-1].className='g-calendar-on';
        }

        // 对所有文章遍历,然后将有文章的日期加上链接,如果文章太多的话,生成页面会很大,去掉了
        
    },
    // next切换
    next:function(){
        var _that = this;
        this.oNext.onclick = function(){
            _that.month++;
            if(_that.month>11){
                _that.month = 0;
                _that.year++;
            }
            // 填充日历
            _that.fillDate(_that.year,_that.month);
        };
    },
    // back 切换
    back:function(){
        var _that = this;
        if(this.oBack != undefined) {
            this.oBack.onclick = function(){
                var postdate = new Date("Tue May 12 2015 10:03:26 GMT+0800"); 
                _that.year = postdate.getFullYear();
                _that.month = postdate.getMonth();
                // 填充日历
                _that.fillDate(_that.year,_that.month);
            };
        }
    },
    // now切换
    now:function(){
        var _that = this;
        if(this.oNow != undefined ) {
            this.oNow.onclick = function(){
                var nowDate = new Date(); 
                _that.year = nowDate.getFullYear();
                _that.month = nowDate.getMonth();
                // 填充日历
                _that.fillDate(_that.year,_that.month);
            };
        }
    },
    // prev切换
    prev:function(){
        var _that = this;
        this.oPrev.onclick = function(){
            _that.month--;
            if(_that.month<0){
                _that.month = 11;
                _that.year--;
            }
            // 填充日历
            _that.fillDate(_that.year,_that.month);
        };
    },
    init:function(){
        this.oTit.innerHTML = '<span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span>';
        // 获取今天的日历时间
        var now = new Date();
        this.c_year = this.year = now.getFullYear();
        this.c_month = this.month = now.getMonth();
        this.date = now.getDate();
        // 初始化--填充日历
        this.fillDate(this.year,this.month);
        //next切换
        this.next();
        //prev切换
        this.prev();
        //back 切换
        this.back();
        //now 切换
        this.now();
    }
}
new LGY_calendar('g-calendar');
</script>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">Recent Posts</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2015/09/01/javascript-summary/">JavaScript 总结</a>
          </li>
        
          <li>
            <a href="/2015/08/28/javascript-oop-function-expression-and-async/">JavaScript 面向对象程序设计、函数表达式和异步编程</a>
          </li>
        
          <li>
            <a href="/2015/08/28/javascript-reference-type/">JavaScript 引用类型</a>
          </li>
        
          <li>
            <a href="/2015/08/28/javascript-grammar/">JavaScript 基本语法</a>
          </li>
        
          <li>
            <a href="/2015/08/10/java-web/">Java Web 笔记</a>
          </li>
        
      </ul>
    </div>
  </div>


  
    
<div class="widget-wrap">
  <h3 class="widget-title">Recent Comments</h3>
  <ul class="widget ds-recent-comments" data-num-items="5" data-show-avatars="0" data-show-title="1" data-show-time="1"></ul>
</div>
<!-- 需要多说的公用代码 -->


  

</aside>

        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2015 howiefh<br>
      Powered by <a href="http://zespia.tw/hexo/" target="_blank">Hexo</a> and Theme by <a href="https://github.com/howiefh/hexo-theme-landscape-f" target="_blank" title="Landscape-F">Landscape-F</a>
    </div>
  </div>
</footer>

    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">Home</a>
  
    <a href="/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    

<!-- 多说公共JS代码 start (一个网页只需插入一次) -->

<script type="text/javascript">
  var duoshuoQuery = {short_name:"howiefh"};
  (function() {
	var ds = document.createElement('script');
	ds.type = 'text/javascript';ds.async = true;
	ds.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//static.duoshuo.com/embed.js';
	ds.charset = 'UTF-8';
	(document.getElementsByTagName('head')[0] 
		|| document.getElementsByTagName('body')[0]).appendChild(ds);
  })();
</script> 

<!-- 多说公共JS代码 end -->

<!-- 百度分享 start -->

<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":["mshare","douban","bdysc","sqq","qq","hi","baidu","huaban","youdao","sdo","mail","xg","diandian","fx","copy","print"],"bdPic":"","bdStyle":"1","bdSize":"16"},"share":{},"image":{"viewList":["qzone","tsina","tqq","renren","weixin"],"viewText":"分享到：","viewSize":"16"}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>

<!-- 百度分享 end -->

<!--
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="/js/jquery.min.js"></script>
-->
<script src="/js/jquery.min.js" type="text/javascript"></script>


  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css" type="text/css">
  <script src="/fancybox/jquery.fancybox.pack.js" type="text/javascript"></script>


<div class="bottom-btn">

	<a class="icon-gotop" href="javascript:void(0)" title="返回顶部"></a>
	<script src="/js/gotop.js" type="text/javascript"></script>
	<!--
	<script src="/js/gotop.js"></script>
	-->


	<a class="icon-toc-toggle" href="javascript:void(0)" title="文章目录"></a>
	<!--
	<script src="/js/toc_aside_toggle.js"></script>
	-->

</div>
<script src="/js/toc_aside_toggle.js" type="text/javascript"></script>


<script src="/js/script.js" type="text/javascript"></script>

  </div>
</body>
</html>
